Updated VGMStream.

CQTexperiment
Christopher Snowhill 2016-12-08 14:47:10 -08:00
parent 0d6447f3d2
commit 5e7180fe34
15 changed files with 953 additions and 356 deletions

View File

@ -109,8 +109,11 @@ void decode_at3plus(VGMSTREAM *vgmstream,
#endif #endif
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
void decode_ffmpeg(VGMSTREAM *stream, void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels);
sample * outbuf, int32_t samples_to_do, int channels);
void reset_ffmpeg(VGMSTREAM *vgmstream);
void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample);
#endif #endif
void decode_acm(ACMStream * acm, sample * outbuf, void decode_acm(ACMStream * acm, sample * outbuf,

View File

@ -59,18 +59,16 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
} }
} }
void decode_ffmpeg(VGMSTREAM *vgmstream, void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) {
sample * outbuf, int32_t samples_to_do, int channels) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
int bytesPerSample;
int bytesPerFrame;
int frameSize; int frameSize;
int dataSize;
int bytesToRead; int bytesToRead;
int bytesRead; int bytesRead;
int errcode;
uint8_t *targetBuf; uint8_t *targetBuf;
AVFormatContext *formatCtx; AVFormatContext *formatCtx;
@ -78,25 +76,24 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
AVPacket *lastReadPacket; AVPacket *lastReadPacket;
AVFrame *lastDecodedFrame; AVFrame *lastDecodedFrame;
int streamIndex;
int bytesConsumedFromDecodedFrame; int bytesConsumedFromDecodedFrame;
int readNextPacket; int readNextPacket;
int endOfStream; int endOfStream;
int endOfAudio; int endOfAudio;
int toConsume;
int framesReadNow; int framesReadNow;
if (data->totalFrames && data->framesRead >= data->totalFrames) {
/* ignore decode attempts at EOF */
if (data->endOfStream || data->endOfAudio) {
memset(outbuf, 0, samples_to_do * channels * sizeof(sample)); memset(outbuf, 0, samples_to_do * channels * sizeof(sample));
return; return;
} }
frameSize = data->channels * (data->bitsPerSample / 8); bytesPerSample = data->bitsPerSample / 8;
dataSize = 0; bytesPerFrame = channels * bytesPerSample;
frameSize = data->channels * bytesPerSample;
bytesToRead = samples_to_do * frameSize; bytesToRead = samples_to_do * frameSize;
bytesRead = 0; bytesRead = 0;
@ -109,8 +106,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
lastReadPacket = data->lastReadPacket; lastReadPacket = data->lastReadPacket;
lastDecodedFrame = data->lastDecodedFrame; lastDecodedFrame = data->lastDecodedFrame;
streamIndex = data->streamIndex;
bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame; bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame;
readNextPacket = data->readNextPacket; readNextPacket = data->readNextPacket;
@ -120,15 +115,18 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
/* keep reading and decoding packets until the requested number of samples (in bytes) */ /* keep reading and decoding packets until the requested number of samples (in bytes) */
while (bytesRead < bytesToRead) { while (bytesRead < bytesToRead) {
int planeSize; int planeSize;
int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt); int planar;
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, int dataSize;
lastDecodedFrame->nb_samples, int toConsume;
codecCtx->sample_fmt, 1); int errcode;
/* size of previous frame */
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1);
if (dataSize < 0) if (dataSize < 0)
dataSize = 0; dataSize = 0;
/* read packet */ /* read new frame + packets when requested */
while (readNextPacket && !endOfAudio) { while (readNextPacket && !endOfAudio) {
if (!endOfStream) { if (!endOfStream) {
av_packet_unref(lastReadPacket); av_packet_unref(lastReadPacket);
@ -139,10 +137,11 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
if (formatCtx->pb && formatCtx->pb->error) if (formatCtx->pb && formatCtx->pb->error)
break; break;
} }
if (lastReadPacket->stream_index != streamIndex) if (lastReadPacket->stream_index != data->streamIndex)
continue; /* ignore non audio streams */ continue; /* ignore non-selected streams */
} }
/* send compressed packet to decoder (NULL at EOF to "drain") */
if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) { if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) {
if (errcode != AVERROR(EAGAIN)) { if (errcode != AVERROR(EAGAIN)) {
goto end; goto end;
@ -152,13 +151,14 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
readNextPacket = 0; readNextPacket = 0;
} }
/* decode packet */ /* decode packets into frame (checking if we have bytes to consume from previous frame) */
if (dataSize <= bytesConsumedFromDecodedFrame) { if (dataSize <= bytesConsumedFromDecodedFrame) {
if (endOfStream && endOfAudio) if (endOfStream && endOfAudio)
break; break;
bytesConsumedFromDecodedFrame = 0; bytesConsumedFromDecodedFrame = 0;
/* receive uncompressed data from decoder */
if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) { if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) {
if (errcode == AVERROR_EOF) { if (errcode == AVERROR_EOF) {
endOfAudio = 1; endOfAudio = 1;
@ -173,46 +173,45 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
} }
} }
/* size of current frame */
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1); dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1);
if (dataSize < 0) if (dataSize < 0)
dataSize = 0; dataSize = 0;
} }
toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead)); toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead));
/* discard packet if needed (fully or partially) */ /* discard decoded frame if needed (fully or partially) */
if (data->samplesToDiscard) { if (data->samplesToDiscard) {
int samplesToConsume; int samplesDataSize = dataSize / bytesPerFrame;
int bytesPerFrame = ((data->bitsPerSample / 8) * channels);
/* discard all if there are more samples to do than the packet's samples */ if (data->samplesToDiscard >= samplesDataSize) {
if (data->samplesToDiscard >= dataSize / bytesPerFrame) { /* discard all of the frame's samples and continue to the next */
samplesToConsume = dataSize / bytesPerFrame;
}
else {
samplesToConsume = toConsume / bytesPerFrame;
}
if (data->samplesToDiscard >= samplesToConsume) { /* full discard: skip to next */
data->samplesToDiscard -= samplesToConsume;
bytesConsumedFromDecodedFrame = dataSize; bytesConsumedFromDecodedFrame = dataSize;
data->samplesToDiscard -= samplesDataSize;
continue; continue;
} }
else { /* partial discard: copy below */ else {
bytesConsumedFromDecodedFrame += data->samplesToDiscard * bytesPerFrame; /* discard part of the frame and copy the rest below */
toConsume -= data->samplesToDiscard * bytesPerFrame; int bytesToDiscard = data->samplesToDiscard * bytesPerFrame;
int dataSizeLeft = dataSize - bytesToDiscard;
bytesConsumedFromDecodedFrame += bytesToDiscard;
data->samplesToDiscard = 0; data->samplesToDiscard = 0;
if (toConsume > dataSizeLeft)
toConsume = dataSizeLeft; /* consume at most dataSize left */
} }
} }
/* copy packet to buffer (mux channels if needed) */ /* copy decoded frame to buffer (mux channels if needed) */
planar = av_sample_fmt_is_planar(codecCtx->sample_fmt);
if (!planar || channels == 1) { if (!planar || channels == 1) {
memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume); memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume);
} }
else { else {
uint8_t * out = (uint8_t *) targetBuf + bytesRead; uint8_t * out = (uint8_t *) targetBuf + bytesRead;
int bytesPerSample = data->bitsPerSample / 8;
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels; int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels;
int toConsumePerPlane = toConsume / channels; int toConsumePerPlane = toConsume / channels;
int s, ch; int s, ch;
@ -231,11 +230,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
end: end:
framesReadNow = bytesRead / frameSize; framesReadNow = bytesRead / frameSize;
if (data->totalFrames && (data->framesRead + framesReadNow > data->totalFrames)) {
framesReadNow = (int)(data->totalFrames - data->framesRead);
}
data->framesRead += framesReadNow;
// Convert the audio // Convert the audio
convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint); convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint);
@ -252,4 +246,70 @@ end:
} }
} }
void reset_ffmpeg(VGMSTREAM *vgmstream) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
if (data->formatCtx) {
avformat_seek_file(data->formatCtx, data->streamIndex, 0, 0, 0, AVSEEK_FLAG_ANY);
}
if (data->codecCtx) {
avcodec_flush_buffers(data->codecCtx);
}
data->readNextPacket = 1;
data->bytesConsumedFromDecodedFrame = INT_MAX;
data->endOfStream = 0;
data->endOfAudio = 0;
data->samplesToDiscard = 0;
}
void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
int64_t ts;
#ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING
/* Seek to loop start by timestamp (closest frame) + adjust skipping some samples */
/* FFmpeg seeks by ts by design (since not all containers can accurately skip to a frame). */
/* TODO: this seems to be off by +-1 frames in some cases */
ts = num_sample;
if (ts >= data->sampleRate * 2) {
data->samplesToDiscard = data->sampleRate * 2;
ts -= data->samplesToDiscard;
}
else {
data->samplesToDiscard = (int)ts;
ts = 0;
}
/* todo fix this properly */
if (data->totalFrames) {
ts = (int)ts * (data->formatCtx->duration) / data->totalFrames;
} else {
data->samplesToDiscard = num_sample;
ts = 0;
}
avformat_seek_file(data->formatCtx, data->streamIndex, ts - 1000, ts, ts, AVSEEK_FLAG_ANY);
avcodec_flush_buffers(data->codecCtx);
#endif /* ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING */
#ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING
/* Start from 0 and discard samples until loop_start for accurate looping (slower but not too noticeable) */
/* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard
* some samples, which is fast but would need calculations per format / when frame size is not constant */
data->samplesToDiscard = num_sample;
ts = 0;
avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY);
avcodec_flush_buffers(data->codecCtx);
#endif /* ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING */
data->readNextPacket = 1;
data->bytesConsumedFromDecodedFrame = INT_MAX;
data->endOfStream = 0;
data->endOfAudio = 0;
}
#endif #endif

View File

@ -470,9 +470,15 @@ static struct {
/* Phantasy Star Online 2 /* Phantasy Star Online 2
* guessed with degod */ * guessed with degod */
{0x07d2,0x1ec5,0x0c7f}, {0x07d2,0x1ec5,0x0c7f},
/* Dragon Ball Z: Dokkan Battle /* Dragon Ball Z: Dokkan Battle
* guessed with degod */ * guessed with degod */
{0x0003,0x0d19,0x043b}, {0x0003,0x0d19,0x043b},
/* Kisou Ryouhei Gunhound EX (2013-01-31)(Dracue)[PSP]
* guessed with degod */
{0x0005,0x0bcd,0x1add},
}; };
static const int keys_8_count = sizeof(keys_8)/sizeof(keys_8[0]); static const int keys_8_count = sizeof(keys_8)/sizeof(keys_8[0]);
@ -487,6 +493,21 @@ static int find_key(STREAMFILE *file, uint8_t type, uint16_t *xor_start, uint16_
int startoff, endoff; int startoff, endoff;
int rc = 0; int rc = 0;
/* try to find key in external file first */
{
uint8_t keybuf[6];
if ( read_key_file(keybuf, 6, file) ) {
*xor_start = get_16bitBE(keybuf+0);
*xor_mult = get_16bitBE(keybuf+2);
*xor_add = get_16bitBE(keybuf+4);
return 1;
}
}
/* guess key from the tables above */
startoff=read_16bitBE(2, file)+4; startoff=read_16bitBE(2, file)+4;
endoff=(read_32bitBE(12, file)+31)/32*18*read_8bit(7, file)+startoff; endoff=(read_32bitBE(12, file)+31)/32*18*read_8bit(7, file)+startoff;

View File

@ -4,8 +4,18 @@
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
/* internal sizes, can be any value */
#define FFMPEG_DEFAULT_BLOCK_SIZE 2048
#define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024
static int init_seek(ffmpeg_codec_data * data);
static volatile int g_ffmpeg_initialized = 0; static volatile int g_ffmpeg_initialized = 0;
/*
* Global FFmpeg init
*/
static void g_init_ffmpeg() static void g_init_ffmpeg()
{ {
if (g_ffmpeg_initialized == 1) if (g_ffmpeg_initialized == 1)
@ -22,24 +32,19 @@ static void g_init_ffmpeg()
} }
} }
ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t stream_offset, uint64_t stream_size, int fmt_big_endian);
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
return init_ffmpeg_faux_riff(streamFile, -1, start, size, 0);
}
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
/**
* Generic init FFmpeg and vgmstream for any file supported by FFmpeg.
* Always called by vgmstream when trying to identify the file type (if the player allows it).
*/
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) {
return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) ); return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) );
} }
void free_ffmpeg(ffmpeg_codec_data *data);
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
VGMSTREAM *vgmstream = NULL; VGMSTREAM *vgmstream = NULL;
ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size); ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size);
if (!data) return NULL; if (!data) return NULL;
vgmstream = allocate_vgmstream(data->channels, 0); vgmstream = allocate_vgmstream(data->channels, 0);
@ -54,22 +59,34 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start,
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_FFmpeg; vgmstream->meta_type = meta_FFmpeg;
/* this may happen for some streams */
if (vgmstream->num_samples <= 0)
goto fail;
return vgmstream; return vgmstream;
fail: fail:
free_ffmpeg(data); free_ffmpeg(data);
if (vgmstream) {
vgmstream->codec_data = NULL;
close_vgmstream(vgmstream);
}
return NULL; return NULL;
} }
/**
* AVIO callback: read stream, skipping external headers if needed
*/
static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size) static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size)
{ {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque; ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque;
uint64_t offset = data->offset; uint64_t offset = data->offset;
int max_to_copy; int max_to_copy = 0;
int ret; int ret;
if (data->header_insert_block) { if (data->header_insert_block) {
max_to_copy = 0;
if (offset < data->header_size) { if (offset < data->header_size) {
max_to_copy = (int)(data->header_size - offset); max_to_copy = (int)(data->header_size - offset);
if (max_to_copy > buf_size) { if (max_to_copy > buf_size) {
@ -100,11 +117,17 @@ static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size)
return ret; return ret;
} }
/**
* AVIO callback: write stream not needed
*/
static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size) static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size)
{ {
return -1; return -1;
} }
/**
* AVIO callback: seek stream, skipping external headers if needed
*/
static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
{ {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque; ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque;
@ -112,7 +135,11 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
return data->size + data->header_size; return data->size + data->header_size;
} }
whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE); whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE);
/* false offsets, on reads data->start will be added */
switch (whence) { switch (whence) {
case SEEK_SET:
break;
case SEEK_CUR: case SEEK_CUR:
offset += data->offset; offset += data->offset;
break; break;
@ -128,6 +155,19 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
return data->offset = offset; return data->offset = offset;
} }
/**
* Manually init FFmpeg only, from an offset.
* Can be used if the stream has an extra header over data recognized by FFmpeg.
*/
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
return init_ffmpeg_faux_riff(streamFile, -1, start, size, 0);
}
/**
* Manually init FFmpeg only, from an offset / fake RIFF.
* Can insert a fake RIFF header, to trick FFmpeg into demuxing/decoding the stream.
*/
ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t start, uint64_t size, int big_endian) { ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t start, uint64_t size, int big_endian) {
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
@ -136,15 +176,17 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
int errcode, i; int errcode, i;
int streamIndex; int streamIndex;
AVStream *stream;
AVCodecParameters *codecPar; AVCodecParameters *codecPar;
AVRational tb; AVRational tb;
/* basic setup */
g_init_ffmpeg(); g_init_ffmpeg();
data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data)); data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data));
if (!data) return NULL;
if (!data) return 0;
streamFile->get_name( streamFile, filename, sizeof(filename) ); streamFile->get_name( streamFile, filename, sizeof(filename) );
@ -154,6 +196,8 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
data->start = start; data->start = start;
data->size = size; data->size = size;
/* insert fake RIFF header to trick FFmpeg into demuxing/decoding the stream */
if (fmt_offset > 0) { if (fmt_offset > 0) {
int max_header_size = (int)(start - fmt_offset); int max_header_size = (int)(start - fmt_offset);
uint8_t *p; uint8_t *p;
@ -188,10 +232,12 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
put_32bitLE(p + data->header_size - 4, size); put_32bitLE(p + data->header_size - 4, size);
} }
data->buffer = av_malloc(128 * 1024);
/* setup IO, attempt to autodetect format and gather some info */
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
if (!data->buffer) goto fail; if (!data->buffer) goto fail;
data->ioCtx = avio_alloc_context(data->buffer, 128 * 1024, 0, data, ffmpeg_read, ffmpeg_write, ffmpeg_seek); data->ioCtx = avio_alloc_context(data->buffer, FFMPEG_DEFAULT_IO_BUFFER_SIZE, 0, data, ffmpeg_read, ffmpeg_write, ffmpeg_seek);
if (!data->ioCtx) goto fail; if (!data->ioCtx) goto fail;
data->formatCtx = avformat_alloc_context(); data->formatCtx = avformat_alloc_context();
@ -199,30 +245,37 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
data->formatCtx->pb = data->ioCtx; data->formatCtx->pb = data->ioCtx;
if ((errcode = avformat_open_input(&data->formatCtx, "", NULL, NULL)) < 0) goto fail; if ((errcode = avformat_open_input(&data->formatCtx, "", NULL, NULL)) < 0) goto fail; /* autodetect */
if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail; if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail;
/* find valid audio stream inside */
streamIndex = -1; streamIndex = -1;
for (i = 0; i < data->formatCtx->nb_streams; ++i) { for (i = 0; i < data->formatCtx->nb_streams; ++i) {
codecPar = data->formatCtx->streams[i]->codecpar; stream = data->formatCtx->streams[i];
if (codecPar->codec_type == AVMEDIA_TYPE_AUDIO) { codecPar = stream->codecpar;
streamIndex = i; if (streamIndex < 0 && codecPar->codec_type == AVMEDIA_TYPE_AUDIO) {
break; streamIndex = i; /* select first audio stream found */
} else {
stream->discard = AVDISCARD_ALL; /* disable demuxing unneded streams */
} }
} }
if (streamIndex < 0) goto fail; if (streamIndex < 0) goto fail;
data->streamIndex = streamIndex; data->streamIndex = streamIndex;
stream = data->formatCtx->streams[streamIndex];
/* prepare codec and frame/packet buffers */
data->codecCtx = avcodec_alloc_context3(NULL); data->codecCtx = avcodec_alloc_context3(NULL);
if (!data->codecCtx) goto fail; if (!data->codecCtx) goto fail;
if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail; if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail;
av_codec_set_pkt_timebase(data->codecCtx, data->formatCtx->streams[streamIndex]->time_base); av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
data->codec = avcodec_find_decoder(data->codecCtx->codec_id); data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
if (!data->codec) goto fail; if (!data->codec) goto fail;
@ -232,12 +285,16 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
data->lastDecodedFrame = av_frame_alloc(); data->lastDecodedFrame = av_frame_alloc();
if (!data->lastDecodedFrame) goto fail; if (!data->lastDecodedFrame) goto fail;
av_frame_unref(data->lastDecodedFrame); av_frame_unref(data->lastDecodedFrame);
data->lastReadPacket = malloc(sizeof(AVPacket)); data->lastReadPacket = malloc(sizeof(AVPacket));
if (!data->lastReadPacket) goto fail; if (!data->lastReadPacket) goto fail;
av_new_packet(data->lastReadPacket, 0); av_new_packet(data->lastReadPacket, 0);
data->readNextPacket = 1; data->readNextPacket = 1;
data->bytesConsumedFromDecodedFrame = INT_MAX; data->bytesConsumedFromDecodedFrame = INT_MAX;
/* other setup */
data->sampleRate = data->codecCtx->sample_rate; data->sampleRate = data->codecCtx->sample_rate;
data->channels = data->codecCtx->channels; data->channels = data->codecCtx->channels;
data->floatingPoint = 0; data->floatingPoint = 0;
@ -274,21 +331,28 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
goto fail; goto fail;
} }
tb.num = 1; tb.den = data->codecCtx->sample_rate;
data->totalFrames = av_rescale_q(data->formatCtx->streams[streamIndex]->duration, data->formatCtx->streams[streamIndex]->time_base, tb);
data->bitrate = (int)(data->codecCtx->bit_rate); data->bitrate = (int)(data->codecCtx->bit_rate);
data->framesRead = 0;
data->endOfStream = 0; data->endOfStream = 0;
data->endOfAudio = 0; data->endOfAudio = 0;
/* try to guess frames/samples (duration isn't always set) */
tb.num = 1; tb.den = data->codecCtx->sample_rate;
data->totalFrames = av_rescale_q(stream->duration, stream->time_base, tb);
if (data->totalFrames < 0) if (data->totalFrames < 0)
data->totalFrames = 0; data->totalFrames = 0; /* caller must consider this */
data->sampleBuffer = av_malloc( 2048 * (data->bitsPerSample / 8) * data->channels ); /* setup decode buffer */
data->samplesPerBlock = FFMPEG_DEFAULT_BLOCK_SIZE;
data->sampleBuffer = av_malloc( data->samplesPerBlock * (data->bitsPerSample / 8) * data->channels );
if (!data->sampleBuffer) if (!data->sampleBuffer)
goto fail; goto fail;
/* setup decent seeking for faulty formats */
errcode = init_seek(data);
if (errcode < 0) goto fail;
return data; return data;
fail: fail:
@ -297,6 +361,83 @@ fail:
return NULL; return NULL;
} }
/**
* Special patching for FFmpeg's buggy seek code.
*
* To seek with avformat_seek_file/av_seek_frame, FFmpeg's demuxers can implement read_seek2 (newest API)
* or read_seek (older API), with various search modes. If none are available it will use seek_frame_generic,
* which manually reads frame by frame until the selected timestamp. However, the prev frame will be consumed
* (so after seeking to 0 next av_read_frame will actually give the second frame and so on).
*
* Fortunately seek_frame_generic can use an index to find the correct position. This function reads the
* first frame/packet and sets up index to timestamp 0. This ensures faulty demuxers will seek to 0 correctly.
* Some formats may not seek to 0 even with this, though.
*/
static int init_seek(ffmpeg_codec_data * data) {
int ret, ts_index, found_first = 0;
int64_t ts = 0;
int64_t pos; /* offset */
int size; /* coded size */
int distance = 0; /* always? */
AVStream * stream;
AVPacket * pkt;
stream = data->formatCtx->streams[data->streamIndex];
pkt = data->lastReadPacket;
/* read_seek shouldn't need this index, but direct access to FFmpeg's internals is no good */
/* if (data->formatCtx->iformat->read_seek || data->formatCtx->iformat->read_seek2)
return 0; */
/* some formats already have a proper index (e.g. M4A) */
ts_index = av_index_search_timestamp(stream, ts, AVSEEK_FLAG_ANY);
if (ts_index>=0)
goto test_seek;
/* find the first + second packets to get pos/size */
while (1) {
av_packet_unref(pkt);
ret = av_read_frame(data->formatCtx, pkt);
if (ret < 0)
goto fail;
if (pkt->stream_index != data->streamIndex)
continue; /* ignore non-selected streams */
if (!found_first) { /* first found */
found_first = 1;
pos = pkt->pos;
continue;
} else { /* second found */
size = pkt->pos - pos; /* coded, pkt->size is decoded size */
break;
}
}
/* add index 0 */
ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME);
if ( ret < 0 )
return ret;
test_seek:
/* seek to 0 test / move back to beginning, since we just consumed packets */
ret = avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY);
if ( ret < 0 )
return ret; /* we can't even reset_vgmstream the file */
avcodec_flush_buffers(data->codecCtx);
return 0;
fail:
return -1;
}
void free_ffmpeg(ffmpeg_codec_data *data) { void free_ffmpeg(ffmpeg_codec_data *data) {
if (data->lastReadPacket) { if (data->lastReadPacket) {
av_packet_unref(data->lastReadPacket); av_packet_unref(data->lastReadPacket);

View File

@ -9,9 +9,8 @@ VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile) {
} }
VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
/* These I don't know about... */ unsigned int ciphKey1;
static const unsigned int ciphKey1=0x30DBE1AB; unsigned int ciphKey2;
static const unsigned int ciphKey2=0xCC554639;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
@ -51,6 +50,20 @@ VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, ui
hca = (clHCA *)(hca_file + 1); hca = (clHCA *)(hca_file + 1);
/* try to find key in external file */
{
uint8_t keybuf[8];
if ( read_key_file(keybuf, 8, streamFile) ) {
ciphKey2 = get_32bitBE(keybuf+0);
ciphKey1 = get_32bitBE(keybuf+4);
} else {
/* PSO2 */
ciphKey2=0xCC554639;
ciphKey1=0x30DBE1AB;
}
}
clHCA_clear(hca, ciphKey1, ciphKey2); clHCA_clear(hca, ciphKey1, ciphKey2);
if (clHCA_Decode(hca, hca_data, header_size, 0) < 0) goto fail; if (clHCA_Decode(hca, hca_data, header_size, 0) < 0) goto fail;

View File

@ -124,6 +124,8 @@ void free_ffmpeg(ffmpeg_codec_data *);
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile);
#endif #endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
@ -587,7 +589,7 @@ VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps3_past(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps3_past(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps3_sgh_sgb(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps3_sgdx(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ngca(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ngca(STREAMFILE* streamFile);
@ -621,16 +623,12 @@ VGMSTREAM * init_vgmstream_eb_sf0(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps3_klbs(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps3_klbs(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps3_sgx(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_tun(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_tun(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_wpd(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_wpd(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps3_sgd(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_mn_str(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_mn_str(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_mss(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps2_mss(STREAMFILE* streamFile);

View File

@ -161,3 +161,79 @@ fail:
} }
#endif #endif
#endif #endif
#ifdef VGM_USE_FFMPEG
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset = 0;
int loop_flag = 0;
int32_t num_samples = 0, loop_start_sample = 0, loop_end_sample = 0;
ffmpeg_codec_data *ffmpeg_data = NULL;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if ( strcasecmp("mp4",filename_extension(filename))
&& strcasecmp("m4a",filename_extension(filename))
&& strcasecmp("m4v",filename_extension(filename))
&& strcasecmp("bin",filename_extension(filename)) ) /* Final Fantasy Dimensions iOS */
goto fail;
/* check header for Final Fantasy Dimensions */
if (read_32bitBE(0x00,streamFile) == 0x4646444C) { /* "FFDL" (any kind of FFD file) */
if (read_32bitBE(0x04,streamFile) == 0x6D747873) { /* "mtxs" (bgm file) */
num_samples = read_32bitLE(0x08,streamFile);
loop_start_sample = read_32bitLE(0x0c,streamFile);
loop_end_sample = read_32bitLE(0x10,streamFile);
loop_flag = !(loop_start_sample==0 && loop_end_sample==num_samples);
start_offset = 0x14;
} else {
start_offset = 0x4; /* some SEs */
}
/* todo some FFDL have multi streams ("FFLD" + mtxsdata1 + mp4data1 + mtxsdata2 + mp4data2 + etc) */
}
/* check header */
if ( read_32bitBE(start_offset+0x04,streamFile) != 0x66747970) /* size 0x00 + "ftyp" 0x04 */
goto fail;
ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, streamFile->get_size(streamFile));
if ( !ffmpeg_data ) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(ffmpeg_data->channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->num_samples = ffmpeg_data->totalFrames; /* todo compare with FFD num_samples*/
vgmstream->sample_rate = ffmpeg_data->sampleRate;
vgmstream->channels = ffmpeg_data->channels;
if (loop_flag) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
}
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_FFmpeg;
vgmstream->codec_data = ffmpeg_data;
return vgmstream;
fail:
/* clean up anything we may have opened */
if (ffmpeg_data) {
free_ffmpeg(ffmpeg_data);
if (vgmstream) vgmstream->codec_data = NULL;
}
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}
#endif

View File

@ -6,33 +6,59 @@
VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
off_t start_offset; off_t start_offset, header_offset = 0;
int32_t loop_start, loop_end; int32_t data_size, loop_start, loop_end;
int loop_flag = 0; int loop_flag = 0;
int channel_count; int channel_count;
int codec_id; int codec_id;
size_t fileLength;
#ifdef VGM_USE_FFMPEG
ffmpeg_codec_data *ffmpeg_data = NULL;
#endif
/* check extension, case insensitive */ /* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename)); streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("msf",filename_extension(filename))) goto fail; if (strcasecmp("msf",filename_extension(filename))) goto fail;
if (read_8bit(0x0,streamFile) != 0x4D) goto fail; /* M */ /* "WMSF" variation with a mini header over the MSFC header, same extension */
if (read_8bit(0x1,streamFile) != 0x53) goto fail; /* S */ if (read_32bitBE(0x00,streamFile) == 0x574D5346) {
if (read_8bit(0x2,streamFile) != 0x46) goto fail; /* F */ header_offset = 0x10;
}
start_offset = header_offset+0x40;
fileLength = get_streamfile_size(streamFile); /* usually 0x4D534643 "MSFC" */
if (read_8bit(header_offset+0x0,streamFile) != 0x4D) goto fail; /* M */
if (read_8bit(header_offset+0x1,streamFile) != 0x53) goto fail; /* S */
if (read_8bit(header_offset+0x2,streamFile) != 0x46) goto fail; /* F */
loop_flag = (read_32bitBE(0x18,streamFile) != 0xFFFFFFFF);
if (loop_flag)
{ data_size = read_32bitBE(header_offset+0x0C,streamFile); /* without header*/
loop_start = read_32bitBE(0x18,streamFile); if (data_size==0xFFFFFFFF) {
loop_end = read_32bitBE(0x0C,streamFile); size_t fileLength = get_streamfile_size(streamFile);
data_size = fileLength - start_offset;
} }
channel_count = read_32bitBE(0x8,streamFile); /* block_align/loop_type? = read_32bitBE(header_offset+0x14,streamFile);*/ /* 00/40 when no loop, 11/50/51/71 */
codec_id = read_32bitBE(0x4,streamFile);
loop_start = read_32bitBE(header_offset+0x18,streamFile);
loop_end = read_32bitBE(header_offset+0x1C,streamFile); /* loop duration */
loop_flag = loop_start != 0xFFFFFFFF;
if (loop_flag) {
if (loop_end==0xFFFFFFFF) {/* not seen */
loop_end = data_size;
} else {
loop_end = loop_start + loop_end; /* usually equals data_size but not always */
if ( loop_end > data_size)/* not seen */
loop_end = data_size;
}
}
channel_count = read_32bitBE(header_offset+0x8,streamFile);
codec_id = read_32bitBE(header_offset+0x4,streamFile);
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream = allocate_vgmstream(channel_count,loop_flag);
@ -42,18 +68,19 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
vgmstream->channels = channel_count; vgmstream->channels = channel_count;
/* Sample rate hack for strange files that don't have a specified frequency */ /* Sample rate hack for strange files that don't have a specified frequency */
if (read_32bitBE(0x10,streamFile)==0x00000000) if (read_32bitBE(header_offset+0x10,streamFile)==0x00000000)
vgmstream->sample_rate = 48000; vgmstream->sample_rate = 48000;
else else
vgmstream->sample_rate = read_32bitBE(0x10,streamFile); vgmstream->sample_rate = read_32bitBE(header_offset+0x10,streamFile);
start_offset = 0x40;
vgmstream->meta_type = meta_PS3_MSF;
switch (codec_id) { switch (codec_id) {
case 0x0: /* PCM (Big Endian) */ case 0x0: /* PCM (Big Endian) */
{ {
vgmstream->coding_type = coding_PCM16BE; vgmstream->coding_type = coding_PCM16BE;
vgmstream->num_samples = read_32bitBE(0x0C,streamFile)/2/channel_count; vgmstream->num_samples = data_size/2/channel_count;
if (loop_flag){ if (loop_flag){
vgmstream->loop_start_sample = loop_start/2/channel_count; vgmstream->loop_start_sample = loop_start/2/channel_count;
@ -74,12 +101,7 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
case 0x3: /* PSx ADPCM */ case 0x3: /* PSx ADPCM */
{ {
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitBE(0x0C,streamFile)*28/16/channel_count; vgmstream->num_samples = data_size*28/16/channel_count;
if (vgmstream->num_samples == 0xFFFFFFFF)
{
vgmstream->num_samples = (fileLength - start_offset)*28/16/channel_count;
}
if (loop_flag) if (loop_flag)
{ {
@ -94,13 +116,66 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
else if (channel_count > 1) else if (channel_count > 1)
{ {
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10; // read_32bitBE(0x14,streamFile); vgmstream->interleave_block_size = 0x10;
} }
} }
break; break;
#ifdef VGM_USE_MPEG #ifdef VGM_USE_FFMPEG
case 0x4: /* ATRAC3 (frame size 96) */
case 0x5: /* ATRAC3 (frame size 152) */
case 0x6: /* ATRAC3 (frame size 192) */
/* delegate to FFMpeg, it can parse MSF files */
ffmpeg_data = init_ffmpeg_offset(streamFile, header_offset, streamFile->get_size(streamFile) );
if ( !ffmpeg_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_FFmpeg;
vgmstream->codec_data = ffmpeg_data;
vgmstream->num_samples = ffmpeg_data->totalFrames;
if (loop_flag) {
int atrac3_frame = 1024;
int block_align = (codec_id == 0x4 ? 96 : codec_id == 0x5 ? 152 : 192) * channel_count;
/* int block_align = ffmpeg_data->codecCtx->block_align; *//* is this always set? */
vgmstream->loop_start_sample = (loop_start / block_align) * atrac3_frame;
vgmstream->loop_end_sample = (loop_end / block_align) * atrac3_frame;
}
break;
#endif
#ifdef VGM_USE_FFMPEG
case 0x7: /* MPEG */
/* delegate to FFMpeg, it can parse MSF files */
ffmpeg_data = init_ffmpeg_offset(streamFile, header_offset, streamFile->get_size(streamFile) );
if ( !ffmpeg_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_FFmpeg;
vgmstream->codec_data = ffmpeg_data;
/* todo check CBR better (frame_size=0?) */
/* vgmstream->num_samples = ffmpeg_data->totalFrames; */ /* duration is not set/innacurate for MP3 in FFMpeg */
vgmstream->num_samples = (int64_t)data_size * ffmpeg_data->sampleRate * 8 / ffmpeg_data->bitrate;
if (loop_flag) {
int frame_size = ffmpeg_data->codecCtx->frame_size;
vgmstream->loop_start_sample = (int64_t)loop_start * ffmpeg_data->sampleRate * 8 / ffmpeg_data->bitrate;
vgmstream->loop_start_sample -= vgmstream->loop_start_sample==frame_size ? frame_size
: vgmstream->loop_start_sample % frame_size;
vgmstream->loop_end_sample = (int64_t)loop_end * ffmpeg_data->sampleRate * 8 / ffmpeg_data->bitrate;
vgmstream->loop_end_sample -= vgmstream->loop_end_sample==frame_size ? frame_size
: vgmstream->loop_end_sample % frame_size;
}
break;
#endif
#if defined(VGM_USE_MPEG) && !defined(VGM_USE_FFMPEG)
case 0x7: /* MPEG */ case 0x7: /* MPEG */
{ {
int frame_size = 576; /* todo incorrect looping calcs, MP3 can have other sizes */
mpeg_codec_data *mpeg_data = NULL; mpeg_codec_data *mpeg_data = NULL;
struct mpg123_frameinfo mi; struct mpg123_frameinfo mi;
coding_t ct; coding_t ct;
@ -114,13 +189,13 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
vgmstream->coding_type = ct; vgmstream->coding_type = ct;
vgmstream->layout_type = layout_mpeg; vgmstream->layout_type = layout_mpeg;
if (mi.vbr != MPG123_CBR) goto fail; if (mi.vbr != MPG123_CBR) goto fail;
vgmstream->num_samples = mpeg_bytes_to_samples(read_32bitBE(0xC,streamFile), &mi); vgmstream->num_samples = mpeg_bytes_to_samples(data_size, &mi);
vgmstream->num_samples -= vgmstream->num_samples%576; vgmstream->num_samples -= vgmstream->num_samples % frame_size;
if (loop_flag) { if (loop_flag) {
vgmstream->loop_start_sample = mpeg_bytes_to_samples(loop_start, &mi); vgmstream->loop_start_sample = mpeg_bytes_to_samples(loop_start, &mi);
vgmstream->loop_start_sample -= vgmstream->loop_start_sample%576; vgmstream->loop_start_sample -= vgmstream->loop_start_sample % frame_size;
vgmstream->loop_end_sample = mpeg_bytes_to_samples(loop_end, &mi); vgmstream->loop_end_sample = mpeg_bytes_to_samples(loop_end, &mi);
vgmstream->loop_end_sample -= vgmstream->loop_end_sample%576; vgmstream->loop_end_sample -= vgmstream->loop_end_sample % frame_size;
} }
vgmstream->interleave_block_size = 0; vgmstream->interleave_block_size = 0;
} }
@ -130,7 +205,6 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
goto fail; goto fail;
} }
vgmstream->meta_type = meta_PS3_MSF;
/* open the file for reading */ /* open the file for reading */
{ {
@ -151,6 +225,12 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
/* clean up anything we may have opened */ /* clean up anything we may have opened */
fail: fail:
#ifdef VGM_USE_FFMPEG
if (ffmpeg_data) {
free_ffmpeg(ffmpeg_data);
vgmstream->codec_data = NULL;
}
#endif
if (vgmstream) close_vgmstream(vgmstream); if (vgmstream) close_vgmstream(vgmstream);
return NULL; return NULL;
} }

View File

@ -1,200 +1,334 @@
#include "meta.h" #include "meta.h"
#include "../util.h" #include "../util.h"
/* SGH+SGB (from Folklore) */
VGMSTREAM * init_vgmstream_ps3_sgh_sgb(STREAMFILE *streamFile) { /* utils to fix AT3 looping */
typedef struct {
int32_t fact_samples;
int32_t loop_start_sample;
int32_t loop_end_sample;
int32_t skip_samples;
} at3_riff_info;
static int get_at3_riff_info(at3_riff_info* info, STREAMFILE *streamFile, int32_t offset);
/* Sony's SGB+SGH / SGD / SGX (variations of the same format)
* PS3: Genji (SGX only), Folklore, Afrika, Tokyo Jungle
* PSP: Brave Story, Sarugetchu Sarusaru Daisakusen, Kurohyo 1/2
*
* Contains header + chunks, usually:
* WAVE: stream(s) header of ADPCM, AC3, ATRAC3plus, etc
* NAME: stream name(s)
* WSUR, WMRK, BUSS: unknown
* RGND, SEQD: unknown (related to SE)
* Then data, containing the original header if applicable (ex. AT3 RIFF).
* The SGDX header has priority over it (ex. some ATRAC3plus files have 48000 while the data RIFF 44100)
*/
VGMSTREAM * init_vgmstream_ps3_sgdx(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
off_t start_offset = 0; STREAMFILE * streamHeader = NULL;
STREAMFILE * streamFileSGH = NULL;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
char filenameSGH[PATH_LIMIT];
int channel_count; off_t start_offset, data_offset;
int loop_flag;
int i;
int is_sgx, is_sgd, is_sgb;
int chunk_offset;
int total_streams;
int target_stream = 0; /* usually only SE use substreams */
#ifdef VGM_USE_FFMPEG
ffmpeg_codec_data *ffmpeg_data = NULL;
#endif
/* check extension, case insensitive */ /* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename)); streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("sgb",filename_extension(filename))) goto fail; is_sgx = strcasecmp("sgx",filename_extension(filename))==0;
is_sgd = strcasecmp("sgd",filename_extension(filename))==0;
strcpy(filenameSGH,filename); is_sgb = strcasecmp("sgb",filename_extension(filename))==0;
strcpy(filenameSGH+strlen(filenameSGH)-3,"sgh"); if ( !(is_sgx || is_sgd || is_sgb) )
streamFileSGH = streamFile->open(streamFile,filenameSGH,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!streamFileSGH) goto fail;
/* check header */
if (read_32bitBE(0x00,streamFileSGH) != 0x53475844) /* "SGXD" */
goto fail; goto fail;
channel_count = read_8bit(0x29,streamFileSGH); /* SGB+SGH: use SGH as header; otherwise use the current file as header */
if (read_32bitBE(0x44,streamFileSGH)==0xFFFFFFFF) if (is_sgb) {
loop_flag = 0; char fileheader[PATH_LIMIT];
else
loop_flag = 1;
/* build the VGMSTREAM */ strcpy(fileheader,filename);
vgmstream = allocate_vgmstream(channel_count,loop_flag); strcpy(fileheader+strlen(fileheader)-3,"sgh");
if (!vgmstream) goto fail;
/* fill in the vital statistics */ streamHeader = streamFile->open(streamFile,fileheader,STREAMFILE_DEFAULT_BUFFER_SIZE);
vgmstream->channels = channel_count; if (!streamHeader) goto fail;
vgmstream->sample_rate = read_32bitLE(0x2C,streamFileSGH); } else {
vgmstream->num_samples = read_32bitLE(0xC,streamFileSGH)*28/32; streamHeader = streamFile;
vgmstream->coding_type = coding_PSX;
if(loop_flag) {
vgmstream->loop_start_sample = read_32bitLE(0x44,streamFileSGH);
vgmstream->loop_end_sample = read_32bitLE(0x48,streamFileSGH);
} }
/* SGXD chunk (size 0x10) */
if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */
goto fail;
/* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */
/* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */
/* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */
if (is_sgb) {
data_offset = 0x00;
} else if ( is_sgx ) {
data_offset = read_32bitLE(0x04,streamHeader);
} else {
data_offset = read_32bitLE(0x08,streamHeader);
}
chunk_offset = 0x10;
/* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */
/* the format reads chunks until header_size, but we only want WAVE in the first position meaning BGM */
if (read_32bitBE(chunk_offset+0x00,streamHeader) != 0x57415645) /* "WAVE" */
goto fail;
/* 0x04 SGX: unknown; SGD/SGH: chunk length */
/* 0x08 null */
/* usually only SE containers have multiple streams but just in case... */
total_streams = read_32bitLE(chunk_offset+0x0c,streamHeader);
if (target_stream+1 > total_streams)
goto fail;
/* read stream (skip until target_stream) */
chunk_offset += 0x10;
{
int stream_loop_flag;
int stream_type;
int stream_channels;
int32_t stream_sample_rate;
int32_t stream_num_samples, stream_loop_start_sample, stream_loop_end_sample;
off_t stream_start_offset;
int32_t stream_size;
for (i=0; i < total_streams; i++) {
if (i != target_stream) {
chunk_offset += 0x38; /* next file */
continue;
}
/* 0x00 ? (00/01/02) */
/* 0x04 sometimes global offset to wave_name */
stream_type = read_8bit(chunk_offset+0x08,streamHeader);
stream_channels = read_8bit(chunk_offset+0x09,streamHeader);
/* 0x0a null */
stream_sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader);
/* 0x10 info_type: meaning of the next value
* (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */
/* 0x14 info_value (see above) */
/* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */
/* 0x1c null */
stream_num_samples = read_32bitLE(chunk_offset+0x20,streamHeader);
stream_loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader);
stream_loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader);
stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */
if (is_sgx) {
stream_start_offset = 0x0; /* TODO unknown (not seen multi SGX) */
} else{
stream_start_offset = read_32bitLE(chunk_offset+0x30,streamHeader);
}
/* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */
stream_loop_flag = stream_loop_start_sample!=0xffffffff && stream_loop_end_sample!=0xffffffff;
chunk_offset += 0x38; /* next file */
break;
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(stream_channels,stream_loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->num_samples = stream_num_samples;
vgmstream->sample_rate = stream_sample_rate;
vgmstream->channels = stream_channels;
if (stream_loop_flag) {
vgmstream->loop_start_sample = stream_loop_start_sample;
vgmstream->loop_end_sample = stream_loop_end_sample;
}
vgmstream->meta_type = meta_PS3_SGDX;
switch (stream_type) {
case 0x03: /* PSX ADPCM */
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
if (is_sgx) {
vgmstream->interleave_block_size = 0x10; vgmstream->interleave_block_size = 0x10;
vgmstream->meta_type = meta_PS3_SGH_SGB; } else {
vgmstream->interleave_block_size = stream_size;
}
/* open the file for reading */ break;
#ifdef VGM_USE_FFMPEG
case 0x04: /* ATRAC3plus */
{ {
int i; at3_riff_info info;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset= ffmpeg_data = init_ffmpeg_offset(streamFile, data_offset, streamFile->get_size(streamFile));
vgmstream->ch[i].offset=start_offset+ if ( !ffmpeg_data ) goto fail;
vgmstream->interleave_block_size*i;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
/*vgmstream->meta_type = meta_FFmpeg;*/
vgmstream->codec_data = ffmpeg_data;
/* manually fix looping due to FFmpeg bugs */
if (stream_loop_flag && get_at3_riff_info(&info, streamFile, data_offset)) {
if (vgmstream->num_samples == info.fact_samples) { /* use if looks normal */
/* todo use "skip samples"; for now we just use absolute loop values */
vgmstream->loop_start_sample = info.loop_start_sample;
vgmstream->loop_end_sample = info.loop_end_sample;
vgmstream->num_samples += info.skip_samples; /* to ensure it always reaches loop_end */
} }
} }
return vgmstream; break;
}
#endif
/* clean up anything we may have opened */ case 0x05: /* todo PCM? */
fail:
if (streamFileSGH) close_streamfile(streamFileSGH);
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}
VGMSTREAM * init_vgmstream_ps3_sgx(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag = 0;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("sgx",filename_extension(filename))) goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x53475844) /* "SGXD" */
goto fail; goto fail;
loop_flag = (read_32bitLE(0x44,streamFile) != 0xFFFFFFFF); /*
channel_count = read_8bit(0x29,streamFile); vgmstream->coding_type = coding_PCM16LE;
if (vgmstream->channels > 1) {
/* build the VGMSTREAM */ vgmstream->layout_type = layout_interleave;
vgmstream = allocate_vgmstream(channel_count,loop_flag); vgmstream->interleave_block_size = 0x1;
if (!vgmstream) goto fail; } else {
vgmstream->layout_type = layout_none;
/* fill in the vital statistics */
start_offset = read_32bitLE(0x4,streamFile);
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x2C,streamFile);
vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitLE(0xC,streamFile)/16/channel_count*28;
if (loop_flag) {
vgmstream->loop_start_sample = read_32bitLE(0x44,streamFile);
vgmstream->loop_end_sample = read_32bitLE(0x48,streamFile);
} }
vgmstream->layout_type = layout_interleave; break;
vgmstream->interleave_block_size = read_32bitLE(0x8,streamFile); */
vgmstream->meta_type = meta_PS3_SGX;
#ifdef VGM_USE_FFMPEG
case 0x06: /* AC3 */
ffmpeg_data = init_ffmpeg_offset(streamFile, data_offset, streamFile->get_size(streamFile));
if ( !ffmpeg_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
/*vgmstream->meta_type = meta_FFmpeg;*/
vgmstream->codec_data = ffmpeg_data;
break;
#endif
default:
goto fail;
}
start_offset = data_offset + stream_start_offset;
/* open the file for reading */ /* open the file for reading */
{ {
int i; int i;
STREAMFILE * file; STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail; if (!file) goto fail;
for (i=0;i<channel_count;i++) {
for (i=0; i < vgmstream->channels; i++) {
vgmstream->ch[i].streamfile = file; vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset =
vgmstream->ch[i].channel_start_offset= vgmstream->ch[i].offset =
vgmstream->ch[i].offset=start_offset+ start_offset + vgmstream->interleave_block_size * i;
vgmstream->interleave_block_size*i;
} }
} }
}
return vgmstream; return vgmstream;
/* clean up anything we may have opened */
fail: fail:
#ifdef VGM_USE_FFMPEG
if (ffmpeg_data) {
free_ffmpeg(ffmpeg_data);
vgmstream->codec_data = NULL;
}
#endif
if (is_sgb && streamHeader) close_streamfile(streamHeader);
if (vgmstream) close_vgmstream(vgmstream); if (vgmstream) close_vgmstream(vgmstream);
return NULL; return NULL;
} }
VGMSTREAM * init_vgmstream_ps3_sgd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag = 0;
int channel_count;
/* check extension, case insensitive */ /**
streamFile->get_name(streamFile,filename,sizeof(filename)); * AT3 RIFF headers have a "skip samples at the beginning" value that the decoder should use,
if (strcasecmp("sgd",filename_extension(filename))) goto fail; * and absolute loop values. However the SGDX header loop values assume those samples are skipped.
*
* FFmpeg doesn't support/export this, so we have to manually get the absolute values to fix looping.
*/
static int get_at3_riff_info(at3_riff_info* info, STREAMFILE *streamFile, int32_t offset) {
off_t current_chunk, riff_size;
int data_found = 0;
/* check header */ memset(info, 0, sizeof(at3_riff_info));
if (read_32bitBE(0x00,streamFile) != 0x53475844) /* "SGXD" */
if (read_32bitBE(offset+0x0,streamFile)!=0x52494646 /* "RIFF" */
&& read_32bitBE(offset+0x8,streamFile)!=0x57415645 ) /* "WAVE" */
goto fail; goto fail;
loop_flag = (read_32bitLE(0x44,streamFile) != 0xFFFFFFFF);
channel_count = read_8bit(0x29,streamFile);
/* build the VGMSTREAM */ /* read chunks */
vgmstream = allocate_vgmstream(channel_count,loop_flag); riff_size = read_32bitLE(offset+0x4,streamFile);
if (!vgmstream) goto fail; current_chunk = offset + 0xc;
/* fill in the vital statistics */ while (!data_found && current_chunk < riff_size) {
start_offset = read_32bitLE(0x8,streamFile); uint32_t chunk_type = read_32bitBE(current_chunk,streamFile);
vgmstream->channels = channel_count; off_t chunk_size = read_32bitLE(current_chunk+4,streamFile);
vgmstream->sample_rate = read_32bitLE(0x2C,streamFile);
vgmstream->coding_type = coding_PSX; switch(chunk_type) {
vgmstream->num_samples = read_32bitLE(0x40,streamFile)/16/channel_count*28; case 0x736D706C: /* smpl */
if (loop_flag) { if (read_32bitLE(current_chunk+0x24, streamFile)==0
vgmstream->loop_start_sample = read_32bitLE(0x44,streamFile); || read_32bitLE(current_chunk+0x2c+0x4, streamFile)!=0 )
vgmstream->loop_end_sample = read_32bitLE(0x48,streamFile); goto fail;
info->loop_start_sample = read_32bitLE(current_chunk+0x2c+0x8, streamFile);
info->loop_end_sample = read_32bitLE(current_chunk+0x2c+0xc,streamFile);
break;
case 0x66616374: /* fact */
if (chunk_size == 0x8) {
info->fact_samples = read_32bitLE(current_chunk+0x8, streamFile);
info->skip_samples = read_32bitLE(current_chunk+0xc, streamFile);
} else if (chunk_size == 0xc) {
info->fact_samples = read_32bitLE(current_chunk+0x8, streamFile);
info->skip_samples = read_32bitLE(current_chunk+0x10, streamFile);
} else {
goto fail;
}
break;
case 0x64617461: /* data */
data_found = 1;
break;
default:
break;
} }
vgmstream->layout_type = layout_interleave; current_chunk += 8+chunk_size;
vgmstream->interleave_block_size = read_8bit(0x39,streamFile); // just a guess, all of my samples seem to be 0x10 interleave
vgmstream->meta_type = meta_PS3_SGX;
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
} }
return vgmstream; if (!data_found)
goto fail;
/* found */
return 1;
/* clean up anything we may have opened */
fail: fail:
if (vgmstream) close_vgmstream(vgmstream); /* not found */
return NULL; return 0;
} }

View File

@ -220,6 +220,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
off_t file_size = -1; off_t file_size = -1;
int sample_count = 0; int sample_count = 0;
int fact_sample_count = -1; int fact_sample_count = -1;
int fact_sample_skip = -1;
off_t start_offset = -1; off_t start_offset = -1;
int loop_flag = 0; int loop_flag = 0;
@ -241,13 +242,15 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
/* Ubisoft sns */ /* Ubisoft sns */
int sns = 0; int sns = 0;
/* Sony atrac3 / 3plus */
int at3 = 0;
/* check extension, case insensitive */ /* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename)); streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("wav",filename_extension(filename)) && if (strcasecmp("wav",filename_extension(filename)) &&
strcasecmp("lwav",filename_extension(filename)) strcasecmp("lwav",filename_extension(filename))
#if defined(VGM_USE_MAIATRAC3PLUS) || defined(VGM_USE_FFMPEG) #ifndef VGM_USE_FFMPEG
&& strcasecmp("at3",filename_extension(filename)) && strcasecmp("sgb",filename_extension(filename)) /* SGB has proper support with FFmpeg in ps3_sgdx */
&& strcasecmp("sgb",filename_extension(filename))
#endif #endif
) )
{ {
@ -255,6 +258,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
mwv = 1; mwv = 1;
else if (!strcasecmp("sns",filename_extension(filename))) else if (!strcasecmp("sns",filename_extension(filename)))
sns = 1; sns = 1;
#if defined(VGM_USE_MAIATRAC3PLUS) || defined(VGM_USE_FFMPEG)
else if (!strcasecmp("at3",filename_extension(filename)))
at3 = 1;
#endif
else else
goto fail; goto fail;
} }
@ -348,9 +355,16 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
mwv_ctrl_offset = current_chunk; mwv_ctrl_offset = current_chunk;
break; break;
case 0x66616374: /* fact */ case 0x66616374: /* fact */
if (chunk_size != 4 if (sns && chunk_size == 0x10) {
&& (!(sns && chunk_size == 0x10))) break; fact_sample_count = read_32bitLE(current_chunk+0x8, streamFile);
fact_sample_count = read_32bitLE(current_chunk+8, streamFile); } else if (at3 && chunk_size == 0x8) {
fact_sample_count = read_32bitLE(current_chunk+0x8, streamFile);
fact_sample_skip = read_32bitLE(current_chunk+0xc, streamFile);
} else if (at3 && chunk_size == 0xc) {
fact_sample_count = read_32bitLE(current_chunk+0x8, streamFile);
fact_sample_skip = read_32bitLE(current_chunk+0x10, streamFile);
}
break; break;
default: default:
/* ignorance is bliss */ /* ignorance is bliss */
@ -388,13 +402,19 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) ); ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) );
if ( !ffmpeg_data ) goto fail; if ( !ffmpeg_data ) goto fail;
sample_count = ffmpeg_data->totalFrames; sample_count = ffmpeg_data->totalFrames; /* fact_sample_count */
/* the encoder introduces some garbage (usually silent) samples to skip before the stream
* loop values include the skip samples but fact_sample_count doesn't; add them back to fix some edge loops */
if (fact_sample_skip > 0)
sample_count += fact_sample_skip;
} }
break; break;
#endif #endif
#ifdef VGM_USE_MAIATRAC3PLUS #ifdef VGM_USE_MAIATRAC3PLUS
case coding_AT3plus: case coding_AT3plus:
sample_count = (data_size / fmt.block_size) * 2048 * fmt.channel_count; /* rough total samples, not totally accurate since there are some skipped samples in the beginning
* channels shouldn't matter (mono and stereo encoding produces the same number of frames in ATRAC3plus) */
sample_count = (data_size / fmt.block_size) * 2048; /* number_of_frames by decoded_samples_per_frame */
break; break;
#endif #endif
default: default:
@ -565,7 +585,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
/* clean up anything we may have opened */ /* clean up anything we may have opened */
fail: fail:
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
if (ffmpeg_data) free_ffmpeg(ffmpeg_data); if (ffmpeg_data) {
free_ffmpeg(ffmpeg_data);
if (vgmstream) vgmstream->codec_data = NULL;
}
#endif #endif
if (vgmstream) close_vgmstream(vgmstream); if (vgmstream) close_vgmstream(vgmstream);
return NULL; return NULL;

View File

@ -271,3 +271,82 @@ size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset,
return i+extra_bytes; return i+extra_bytes;
} }
/**
* open file containing decryption keys and copy to buffer
* tries combinations of keynames based on the original filename
*
* returns true if found and copied
*/
int read_key_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile) {
char keyname[PATH_LIMIT];
char filename[PATH_LIMIT];
const char *path, *ext;
STREAMFILE * streamFileKey = NULL;
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strlen(filename)+4 > sizeof(keyname)) goto fail;
/* try to open a keyfile using variations:
* "(name.ext)key" (per song), "(.ext)key" (per folder) */
{
ext = strrchr(filename,'.');
if (ext!=NULL) ext = ext+1;
path = strrchr(filename,DIR_SEPARATOR);
if (path!=NULL) path = path+1;
/* "(name.ext)key" */
strcpy(keyname, filename);
strcat(keyname, "key");
streamFileKey = streamFile->open(streamFile,keyname,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (streamFileKey) goto found;
/* "(name.ext)KEY" */
/*
strcpy(keyname+strlen(keyname)-3,"KEY");
streamFileKey = streamFile->open(streamFile,keyname,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (streamFileKey) goto found;
*/
/* "(.ext)key" */
if (path) {
strcpy(keyname, filename);
keyname[path-filename] = '\0';
strcat(keyname, ".");
} else {
strcpy(keyname, ".");
}
if (ext) strcat(keyname, ext);
strcat(keyname, "key");
streamFileKey = streamFile->open(streamFile,keyname,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (streamFileKey) goto found;
/* "(.ext)KEY" */
/*
strcpy(keyname+strlen(keyname)-3,"KEY");
streamFileKey = streamFile->open(streamFile,keyname,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (streamFileKey) goto found;
*/
goto fail;
}
found:
if (get_streamfile_size(streamFileKey) != bufsize) goto fail;
if (read_streamfile(buf, 0, bufsize, streamFileKey)!=bufsize) goto fail;
close_streamfile(streamFileKey);
return 1;
fail:
if (streamFileKey) close_streamfile(streamFileKey);
return 0;
}

View File

@ -34,6 +34,14 @@
#define STREAMFILE_DEFAULT_BUFFER_SIZE 0x400 #define STREAMFILE_DEFAULT_BUFFER_SIZE 0x400
#ifndef DIR_SEPARATOR
#if defined (_WIN32) || defined (WIN32)
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
#endif
typedef struct _STREAMFILE { typedef struct _STREAMFILE {
size_t (*read)(struct _STREAMFILE *,uint8_t * dest, off_t offset, size_t length); size_t (*read)(struct _STREAMFILE *,uint8_t * dest, off_t offset, size_t length);
size_t (*get_size)(struct _STREAMFILE *); size_t (*get_size)(struct _STREAMFILE *);
@ -141,4 +149,7 @@ static inline STREAMFILE * open_stdio_streamfile(const char * const filename) {
size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset, size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset,
STREAMFILE * infile, int *line_done_ptr); STREAMFILE * infile, int *line_done_ptr);
int read_key_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile);
#endif #endif

View File

@ -303,7 +303,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_fsb_mpeg, init_vgmstream_fsb_mpeg,
init_vgmstream_nub_vag, init_vgmstream_nub_vag,
init_vgmstream_ps3_past, init_vgmstream_ps3_past,
init_vgmstream_ps3_sgh_sgb, init_vgmstream_ps3_sgdx,
init_vgmstream_ngca, init_vgmstream_ngca,
init_vgmstream_wii_ras, init_vgmstream_wii_ras,
init_vgmstream_ps2_spm, init_vgmstream_ps2_spm,
@ -320,11 +320,9 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_eb_sfx, init_vgmstream_eb_sfx,
init_vgmstream_eb_sf0, init_vgmstream_eb_sf0,
init_vgmstream_ps3_klbs, init_vgmstream_ps3_klbs,
init_vgmstream_ps3_sgx,
init_vgmstream_ps2_mtaf, init_vgmstream_ps2_mtaf,
init_vgmstream_tun, init_vgmstream_tun,
init_vgmstream_wpd, init_vgmstream_wpd,
init_vgmstream_ps3_sgd,
init_vgmstream_mn_str, init_vgmstream_mn_str,
init_vgmstream_ps2_mss, init_vgmstream_ps2_mss,
init_vgmstream_ps2_hsf, init_vgmstream_ps2_hsf,
@ -340,6 +338,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_g1l, init_vgmstream_g1l,
init_vgmstream_hca, init_vgmstream_hca,
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
init_vgmstream_mp4_aac_ffmpeg,
init_vgmstream_ffmpeg, init_vgmstream_ffmpeg,
#endif #endif
}; };
@ -361,7 +360,7 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) {
/* fail if there is nothing to play /* fail if there is nothing to play
* (without this check vgmstream can generate empty files) */ * (without this check vgmstream can generate empty files) */
if ( vgmstream->num_samples==0 ) { if (vgmstream->num_samples <= 0) {
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
continue; continue;
} }
@ -514,20 +513,7 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
if (vgmstream->coding_type==coding_FFmpeg) { if (vgmstream->coding_type==coding_FFmpeg) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; reset_ffmpeg(vgmstream);
if (data->formatCtx) {
avformat_seek_file(data->formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_ANY);
}
if (data->codecCtx) {
avcodec_flush_buffers(data->codecCtx);
}
data->readNextPacket = 1;
data->bytesConsumedFromDecodedFrame = INT_MAX;
data->framesRead = 0;
data->endOfStream = 0;
data->endOfAudio = 0;
data->samplesToDiscard = 0;
} }
#endif #endif
@ -1083,9 +1069,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
case coding_FFmpeg: case coding_FFmpeg:
{ {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
if (vgmstream->codec_data) { if (data) {
int64_t samplesRemain = data->totalFrames - data->framesRead; /* must know the full block size for edge loops */
return samplesRemain > 2048 ? 2048 : samplesRemain; return data->samplesPerBlock;
} }
return 0; return 0;
} }
@ -1788,41 +1774,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
} }
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
if (vgmstream->coding_type==coding_FFmpeg) { if (vgmstream->coding_type==coding_FFmpeg) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *)(vgmstream->codec_data); seek_ffmpeg(vgmstream, vgmstream->loop_start_sample);
int64_t ts;
/* Seek to loop start by timestamp (closest frame) + adjust skipping some samples */
/* FFmpeg seeks by ts by design (since not all containers can accurately skip to a frame). */
/* TODO: this seems to be off by +-1 frames in some cases */
ts = vgmstream->loop_start_sample;
if (ts >= data->sampleRate * 2) {
data->samplesToDiscard = data->sampleRate * 2;
ts -= data->samplesToDiscard;
}
else {
data->samplesToDiscard = (int)ts;
ts = 0;
}
data->framesRead = (int)ts;
ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames;
#ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING
/* Start from 0 and discard samples until loop_start for accurate looping (slower but not too noticeable) */
/* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard
* some samples, which is fast but would need calculations per format / when frame size is not constant */
data->samplesToDiscard = vgmstream->loop_start_sample;
data->framesRead = 0;
ts = 0;
#endif /* VGM_USE_FFMPEG_ACCURATE_LOOPING */
avformat_seek_file(data->formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY);
avcodec_flush_buffers(data->codecCtx);
data->readNextPacket = 1;
data->bytesConsumedFromDecodedFrame = INT_MAX;
data->endOfStream = 0;
data->endOfAudio = 0;
} }
#endif /* VGM_USE_FFMPEG */ #endif /* VGM_USE_FFMPEG */
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
@ -1909,7 +1861,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
return; return;
} }
snprintf(temp,TEMPSIZE,"sample rate %d Hz\n" snprintf(temp,TEMPSIZE,"sample rate: %d Hz\n"
"channels: %d\n", "channels: %d\n",
vgmstream->sample_rate,vgmstream->channels); vgmstream->sample_rate,vgmstream->channels);
concatn(length,desc,temp); concatn(length,desc,temp);
@ -2125,11 +2077,14 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
{ {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
if (vgmstream->codec_data) { if (vgmstream->codec_data) {
if (data->codec) { if (data->codec && data->codec->long_name) {
snprintf(temp,TEMPSIZE,data->codec->long_name); snprintf(temp,TEMPSIZE,data->codec->long_name);
} }
else if (data->codec && data->codec->name) {
snprintf(temp,TEMPSIZE,data->codec->name);
}
else { else {
snprintf(temp,TEMPSIZE,"FFmpeg"); snprintf(temp,TEMPSIZE,"FFmpeg (unknown codec)");
} }
} }
else { else {
@ -3190,8 +3145,8 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
case meta_PS3_PAST: case meta_PS3_PAST:
snprintf(temp,TEMPSIZE,"SNDP header"); snprintf(temp,TEMPSIZE,"SNDP header");
break; break;
case meta_PS3_SGH_SGB: case meta_PS3_SGDX:
snprintf(temp,TEMPSIZE,"SGH+SGB SGXD header"); snprintf(temp,TEMPSIZE,"SGXD header");
break; break;
case meta_NGCA: case meta_NGCA:
snprintf(temp,TEMPSIZE,"NGCA header"); snprintf(temp,TEMPSIZE,"NGCA header");
@ -3244,9 +3199,6 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
case meta_PS3_KLBS: case meta_PS3_KLBS:
snprintf(temp,TEMPSIZE,"klBS Header"); snprintf(temp,TEMPSIZE,"klBS Header");
break; break;
case meta_PS3_SGX:
snprintf(temp,TEMPSIZE,"PS3 SGXD/WAVE header");
break;
case meta_PS2_MTAF: case meta_PS2_MTAF:
snprintf(temp,TEMPSIZE,"Konami MTAF header"); snprintf(temp,TEMPSIZE,"Konami MTAF header");
break; break;

View File

@ -13,7 +13,12 @@ enum { PATH_LIMIT = 32768 };
* removing these defines (and the references to the libraries in the * removing these defines (and the references to the libraries in the
* Makefile) */ * Makefile) */
#define VGM_USE_VORBIS #define VGM_USE_VORBIS
/* can be disabled to decode with FFmpeg instead */
#ifndef VGM_DISABLE_MPEG
#define VGM_USE_MPEG #define VGM_USE_MPEG
#endif
/* disabled by default, defined for builds that support it */ /* disabled by default, defined for builds that support it */
#define VGM_USE_G7221 #define VGM_USE_G7221
#define VGM_USE_G719 #define VGM_USE_G719
@ -570,7 +575,7 @@ typedef enum {
meta_PS3_MSF, /* MSF header */ meta_PS3_MSF, /* MSF header */
meta_NUB_VAG, /* VAG from Nub archives */ meta_NUB_VAG, /* VAG from Nub archives */
meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */
meta_PS3_SGH_SGB, /* Folklore (PS3) */ meta_PS3_SGDX, /* Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */
meta_NGCA, /* GoldenEye 007 (Wii) */ meta_NGCA, /* GoldenEye 007 (Wii) */
meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */ meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */
meta_PS2_SPM, /* Lethal Skies Elite Pilot: Team SW */ meta_PS2_SPM, /* Lethal Skies Elite Pilot: Team SW */
@ -588,7 +593,6 @@ typedef enum {
meta_EB_SFX, // Excitebots .sfx meta_EB_SFX, // Excitebots .sfx
meta_EB_SF0, // Excitebots .sf0 meta_EB_SF0, // Excitebots .sf0
meta_PS3_KLBS, // L@VE ONCE (PS3) meta_PS3_KLBS, // L@VE ONCE (PS3)
meta_PS3_SGX, // Boku no Natsuyasumi 3 (PS3)
meta_PS2_MTAF, // Metal Gear Solid 3 MTAF meta_PS2_MTAF, // Metal Gear Solid 3 MTAF
meta_PS2_VAG1, // Metal Gear Solid 3 VAG1 meta_PS2_VAG1, // Metal Gear Solid 3 VAG1
meta_PS2_VAG2, // Metal Gear Solid 3 VAG2 meta_PS2_VAG2, // Metal Gear Solid 3 VAG2
@ -868,6 +872,7 @@ typedef struct {
// inserted header, ie. fake RIFF header // inserted header, ie. fake RIFF header
uint8_t *header_insert_block; uint8_t *header_insert_block;
// header/fake RIFF over the real (parseable by FFmpeg) file start
uint64_t header_size; uint64_t header_size;
// stream info // stream info
@ -875,12 +880,13 @@ typedef struct {
int bitsPerSample; int bitsPerSample;
int floatingPoint; int floatingPoint;
int sampleRate; int sampleRate;
int64_t totalFrames; int64_t totalFrames; // sample count, or 0 if unknown
int64_t framesRead;
int bitrate; int bitrate;
// Intermediate buffer // Intermediate buffer
uint8_t *sampleBuffer; uint8_t *sampleBuffer;
// max samples a block can held (can be less or more than samples per decoded frame)
size_t samplesPerBlock;
// FFmpeg context used for metadata // FFmpeg context used for metadata
AVCodec *codec; AVCodec *codec;

View File

@ -234,7 +234,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) {
+ (NSArray *)fileTypes + (NSArray *)fileTypes
{ {
return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"at3", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mtaf", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sgb", @"sgd", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xma", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"xwma", @"ydsp", @"ymf", @"zsd", @"zwdsp", nil]; return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"at3", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mtaf", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sgb", @"sgd", @"sgx", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xma", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"xwma", @"ydsp", @"ymf", @"zsd", @"zwdsp", @"vgmstream", @"vgms", nil];
} }
+ (NSArray *)mimeTypes + (NSArray *)mimeTypes