Updated VGMStream.
parent
0d6447f3d2
commit
5e7180fe34
|
@ -109,8 +109,11 @@ void decode_at3plus(VGMSTREAM *vgmstream,
|
|||
#endif
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
void decode_ffmpeg(VGMSTREAM *stream,
|
||||
sample * outbuf, int32_t samples_to_do, int channels);
|
||||
void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels);
|
||||
|
||||
void reset_ffmpeg(VGMSTREAM *vgmstream);
|
||||
|
||||
void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample);
|
||||
#endif
|
||||
|
||||
void decode_acm(ACMStream * acm, sample * outbuf,
|
||||
|
|
|
@ -59,18 +59,16 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
|
|||
}
|
||||
}
|
||||
|
||||
void decode_ffmpeg(VGMSTREAM *vgmstream,
|
||||
sample * outbuf, int32_t samples_to_do, int channels) {
|
||||
void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
|
||||
|
||||
int bytesPerSample;
|
||||
int bytesPerFrame;
|
||||
int frameSize;
|
||||
int dataSize;
|
||||
|
||||
int bytesToRead;
|
||||
int bytesRead;
|
||||
|
||||
int errcode;
|
||||
|
||||
uint8_t *targetBuf;
|
||||
|
||||
AVFormatContext *formatCtx;
|
||||
|
@ -78,25 +76,24 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
|
|||
AVPacket *lastReadPacket;
|
||||
AVFrame *lastDecodedFrame;
|
||||
|
||||
int streamIndex;
|
||||
|
||||
int bytesConsumedFromDecodedFrame;
|
||||
|
||||
int readNextPacket;
|
||||
int endOfStream;
|
||||
int endOfAudio;
|
||||
|
||||
int toConsume;
|
||||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
||||
frameSize = data->channels * (data->bitsPerSample / 8);
|
||||
dataSize = 0;
|
||||
bytesPerSample = data->bitsPerSample / 8;
|
||||
bytesPerFrame = channels * bytesPerSample;
|
||||
frameSize = data->channels * bytesPerSample;
|
||||
|
||||
bytesToRead = samples_to_do * frameSize;
|
||||
bytesRead = 0;
|
||||
|
@ -109,8 +106,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
|
|||
lastReadPacket = data->lastReadPacket;
|
||||
lastDecodedFrame = data->lastDecodedFrame;
|
||||
|
||||
streamIndex = data->streamIndex;
|
||||
|
||||
bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame;
|
||||
|
||||
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) */
|
||||
while (bytesRead < bytesToRead) {
|
||||
int planeSize;
|
||||
int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt);
|
||||
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels,
|
||||
lastDecodedFrame->nb_samples,
|
||||
codecCtx->sample_fmt, 1);
|
||||
int planar;
|
||||
int dataSize;
|
||||
int toConsume;
|
||||
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)
|
||||
dataSize = 0;
|
||||
|
||||
/* read packet */
|
||||
/* read new frame + packets when requested */
|
||||
while (readNextPacket && !endOfAudio) {
|
||||
if (!endOfStream) {
|
||||
av_packet_unref(lastReadPacket);
|
||||
|
@ -139,10 +137,11 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
|
|||
if (formatCtx->pb && formatCtx->pb->error)
|
||||
break;
|
||||
}
|
||||
if (lastReadPacket->stream_index != streamIndex)
|
||||
continue; /* ignore non audio streams */
|
||||
if (lastReadPacket->stream_index != data->streamIndex)
|
||||
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 != AVERROR(EAGAIN)) {
|
||||
goto end;
|
||||
|
@ -152,13 +151,14 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
|
|||
readNextPacket = 0;
|
||||
}
|
||||
|
||||
/* decode packet */
|
||||
/* decode packets into frame (checking if we have bytes to consume from previous frame) */
|
||||
if (dataSize <= bytesConsumedFromDecodedFrame) {
|
||||
if (endOfStream && endOfAudio)
|
||||
break;
|
||||
|
||||
bytesConsumedFromDecodedFrame = 0;
|
||||
|
||||
/* receive uncompressed data from decoder */
|
||||
if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) {
|
||||
if (errcode == AVERROR_EOF) {
|
||||
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);
|
||||
|
||||
if (dataSize < 0)
|
||||
dataSize = 0;
|
||||
}
|
||||
|
||||
toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead));
|
||||
|
||||
/* discard packet if needed (fully or partially) */
|
||||
/* discard decoded frame if needed (fully or partially) */
|
||||
if (data->samplesToDiscard) {
|
||||
int samplesToConsume;
|
||||
int bytesPerFrame = ((data->bitsPerSample / 8) * channels);
|
||||
int samplesDataSize = dataSize / bytesPerFrame;
|
||||
|
||||
/* discard all if there are more samples to do than the packet's samples */
|
||||
if (data->samplesToDiscard >= dataSize / bytesPerFrame) {
|
||||
samplesToConsume = dataSize / bytesPerFrame;
|
||||
}
|
||||
else {
|
||||
samplesToConsume = toConsume / bytesPerFrame;
|
||||
}
|
||||
if (data->samplesToDiscard >= samplesDataSize) {
|
||||
/* discard all of the frame's samples and continue to the next */
|
||||
|
||||
if (data->samplesToDiscard >= samplesToConsume) { /* full discard: skip to next */
|
||||
data->samplesToDiscard -= samplesToConsume;
|
||||
bytesConsumedFromDecodedFrame = dataSize;
|
||||
data->samplesToDiscard -= samplesDataSize;
|
||||
|
||||
continue;
|
||||
}
|
||||
else { /* partial discard: copy below */
|
||||
bytesConsumedFromDecodedFrame += data->samplesToDiscard * bytesPerFrame;
|
||||
toConsume -= data->samplesToDiscard * bytesPerFrame;
|
||||
else {
|
||||
/* discard part of the frame and copy the rest below */
|
||||
int bytesToDiscard = data->samplesToDiscard * bytesPerFrame;
|
||||
int dataSizeLeft = dataSize - bytesToDiscard;
|
||||
|
||||
bytesConsumedFromDecodedFrame += bytesToDiscard;
|
||||
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) {
|
||||
memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume);
|
||||
}
|
||||
else {
|
||||
uint8_t * out = (uint8_t *) targetBuf + bytesRead;
|
||||
int bytesPerSample = data->bitsPerSample / 8;
|
||||
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels;
|
||||
int toConsumePerPlane = toConsume / channels;
|
||||
int s, ch;
|
||||
|
@ -231,11 +230,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream,
|
|||
|
||||
end:
|
||||
framesReadNow = bytesRead / frameSize;
|
||||
if (data->totalFrames && (data->framesRead + framesReadNow > data->totalFrames)) {
|
||||
framesReadNow = (int)(data->totalFrames - data->framesRead);
|
||||
}
|
||||
|
||||
data->framesRead += framesReadNow;
|
||||
|
||||
// Convert the audio
|
||||
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
|
||||
|
|
|
@ -470,9 +470,15 @@ static struct {
|
|||
/* Phantasy Star Online 2
|
||||
* guessed with degod */
|
||||
{0x07d2,0x1ec5,0x0c7f},
|
||||
|
||||
/* Dragon Ball Z: Dokkan Battle
|
||||
* guessed with degod */
|
||||
{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]);
|
||||
|
@ -487,6 +493,21 @@ static int find_key(STREAMFILE *file, uint8_t type, uint16_t *xor_start, uint16_
|
|||
int startoff, endoff;
|
||||
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;
|
||||
endoff=(read_32bitBE(12, file)+31)/32*18*read_8bit(7, file)+startoff;
|
||||
|
||||
|
|
|
@ -4,8 +4,18 @@
|
|||
|
||||
#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;
|
||||
|
||||
/*
|
||||
* Global FFmpeg init
|
||||
*/
|
||||
static void g_init_ffmpeg()
|
||||
{
|
||||
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) {
|
||||
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 *vgmstream = NULL;
|
||||
|
||||
ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size);
|
||||
|
||||
if (!data) return NULL;
|
||||
|
||||
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->meta_type = meta_FFmpeg;
|
||||
|
||||
/* this may happen for some streams */
|
||||
if (vgmstream->num_samples <= 0)
|
||||
goto fail;
|
||||
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
free_ffmpeg(data);
|
||||
if (vgmstream) {
|
||||
vgmstream->codec_data = NULL;
|
||||
close_vgmstream(vgmstream);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AVIO callback: read stream, skipping external headers if needed
|
||||
*/
|
||||
static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque;
|
||||
uint64_t offset = data->offset;
|
||||
int max_to_copy;
|
||||
int max_to_copy = 0;
|
||||
int ret;
|
||||
if (data->header_insert_block) {
|
||||
max_to_copy = 0;
|
||||
if (offset < data->header_size) {
|
||||
max_to_copy = (int)(data->header_size - offset);
|
||||
if (max_to_copy > buf_size) {
|
||||
|
@ -100,11 +117,17 @@ static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVIO callback: write stream not needed
|
||||
*/
|
||||
static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVIO callback: seek stream, skipping external headers if needed
|
||||
*/
|
||||
static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
|
||||
{
|
||||
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;
|
||||
}
|
||||
whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE);
|
||||
/* false offsets, on reads data->start will be added */
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
offset += data->offset;
|
||||
break;
|
||||
|
@ -128,6 +155,19 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
|
|||
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) {
|
||||
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 streamIndex;
|
||||
AVStream *stream;
|
||||
AVCodecParameters *codecPar;
|
||||
|
||||
AVRational tb;
|
||||
|
||||
|
||||
/* basic setup */
|
||||
g_init_ffmpeg();
|
||||
|
||||
data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data));
|
||||
|
||||
if (!data) return 0;
|
||||
if (!data) return NULL;
|
||||
|
||||
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->size = size;
|
||||
|
||||
|
||||
/* insert fake RIFF header to trick FFmpeg into demuxing/decoding the stream */
|
||||
if (fmt_offset > 0) {
|
||||
int max_header_size = (int)(start - fmt_offset);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/* find valid audio stream inside */
|
||||
streamIndex = -1;
|
||||
|
||||
for (i = 0; i < data->formatCtx->nb_streams; ++i) {
|
||||
codecPar = data->formatCtx->streams[i]->codecpar;
|
||||
if (codecPar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
streamIndex = i;
|
||||
break;
|
||||
stream = data->formatCtx->streams[i];
|
||||
codecPar = stream->codecpar;
|
||||
if (streamIndex < 0 && codecPar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
streamIndex = i; /* select first audio stream found */
|
||||
} else {
|
||||
stream->discard = AVDISCARD_ALL; /* disable demuxing unneded streams */
|
||||
}
|
||||
}
|
||||
|
||||
if (streamIndex < 0) goto fail;
|
||||
|
||||
data->streamIndex = streamIndex;
|
||||
stream = data->formatCtx->streams[streamIndex];
|
||||
|
||||
|
||||
/* prepare codec and frame/packet buffers */
|
||||
data->codecCtx = avcodec_alloc_context3(NULL);
|
||||
if (!data->codecCtx) 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);
|
||||
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();
|
||||
if (!data->lastDecodedFrame) goto fail;
|
||||
av_frame_unref(data->lastDecodedFrame);
|
||||
|
||||
data->lastReadPacket = malloc(sizeof(AVPacket));
|
||||
if (!data->lastReadPacket) goto fail;
|
||||
av_new_packet(data->lastReadPacket, 0);
|
||||
|
||||
data->readNextPacket = 1;
|
||||
data->bytesConsumedFromDecodedFrame = INT_MAX;
|
||||
|
||||
|
||||
/* other setup */
|
||||
data->sampleRate = data->codecCtx->sample_rate;
|
||||
data->channels = data->codecCtx->channels;
|
||||
data->floatingPoint = 0;
|
||||
|
@ -274,21 +331,28 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of
|
|||
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->framesRead = 0;
|
||||
data->endOfStream = 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)
|
||||
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)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* setup decent seeking for faulty formats */
|
||||
errcode = init_seek(data);
|
||||
if (errcode < 0) goto fail;
|
||||
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
|
@ -297,6 +361,83 @@ fail:
|
|||
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) {
|
||||
if (data->lastReadPacket) {
|
||||
av_packet_unref(data->lastReadPacket);
|
||||
|
|
|
@ -9,9 +9,8 @@ VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile) {
|
|||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
/* These I don't know about... */
|
||||
static const unsigned int ciphKey1=0x30DBE1AB;
|
||||
static const unsigned int ciphKey2=0xCC554639;
|
||||
unsigned int ciphKey1;
|
||||
unsigned int ciphKey2;
|
||||
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
|
@ -51,6 +50,20 @@ VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, ui
|
|||
|
||||
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);
|
||||
|
||||
if (clHCA_Decode(hca, hca_data, header_size, 0) < 0) goto fail;
|
||||
|
|
|
@ -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(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile);
|
||||
#endif
|
||||
|
||||
#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_sgh_sgb(STREAMFILE* streamFile);
|
||||
VGMSTREAM * init_vgmstream_ps3_sgdx(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_sgx(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_tun(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_ps2_mss(STREAMFILE* streamFile);
|
||||
|
|
|
@ -161,3 +161,79 @@ fail:
|
|||
}
|
||||
#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
|
||||
|
|
|
@ -6,33 +6,59 @@
|
|||
VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
int32_t loop_start, loop_end;
|
||||
off_t start_offset, header_offset = 0;
|
||||
int32_t data_size, loop_start, loop_end;
|
||||
int loop_flag = 0;
|
||||
int channel_count;
|
||||
int codec_id;
|
||||
size_t fileLength;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("msf",filename_extension(filename))) goto fail;
|
||||
|
||||
|
||||
if (read_8bit(0x0,streamFile) != 0x4D) goto fail; /* M */
|
||||
if (read_8bit(0x1,streamFile) != 0x53) goto fail; /* S */
|
||||
if (read_8bit(0x2,streamFile) != 0x46) goto fail; /* F */
|
||||
/* "WMSF" variation with a mini header over the MSFC header, same extension */
|
||||
if (read_32bitBE(0x00,streamFile) == 0x574D5346) {
|
||||
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)
|
||||
{
|
||||
loop_start = read_32bitBE(0x18,streamFile);
|
||||
loop_end = read_32bitBE(0x0C,streamFile);
|
||||
|
||||
|
||||
data_size = read_32bitBE(header_offset+0x0C,streamFile); /* without header*/
|
||||
if (data_size==0xFFFFFFFF) {
|
||||
size_t fileLength = get_streamfile_size(streamFile);
|
||||
data_size = fileLength - start_offset;
|
||||
}
|
||||
|
||||
channel_count = read_32bitBE(0x8,streamFile);
|
||||
codec_id = read_32bitBE(0x4,streamFile);
|
||||
/* block_align/loop_type? = read_32bitBE(header_offset+0x14,streamFile);*/ /* 00/40 when no loop, 11/50/51/71 */
|
||||
|
||||
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 */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
|
@ -42,18 +68,19 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
vgmstream->channels = channel_count;
|
||||
|
||||
/* 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;
|
||||
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) {
|
||||
case 0x0: /* PCM (Big Endian) */
|
||||
{
|
||||
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){
|
||||
vgmstream->loop_start_sample = loop_start/2/channel_count;
|
||||
|
@ -74,12 +101,7 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
case 0x3: /* PSx ADPCM */
|
||||
{
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitBE(0x0C,streamFile)*28/16/channel_count;
|
||||
|
||||
if (vgmstream->num_samples == 0xFFFFFFFF)
|
||||
{
|
||||
vgmstream->num_samples = (fileLength - start_offset)*28/16/channel_count;
|
||||
}
|
||||
vgmstream->num_samples = data_size*28/16/channel_count;
|
||||
|
||||
if (loop_flag)
|
||||
{
|
||||
|
@ -94,13 +116,66 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
else if (channel_count > 1)
|
||||
{
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10; // read_32bitBE(0x14,streamFile);
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
}
|
||||
}
|
||||
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 */
|
||||
{
|
||||
int frame_size = 576; /* todo incorrect looping calcs, MP3 can have other sizes */
|
||||
|
||||
mpeg_codec_data *mpeg_data = NULL;
|
||||
struct mpg123_frameinfo mi;
|
||||
coding_t ct;
|
||||
|
@ -114,13 +189,13 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
vgmstream->coding_type = ct;
|
||||
vgmstream->layout_type = layout_mpeg;
|
||||
if (mi.vbr != MPG123_CBR) goto fail;
|
||||
vgmstream->num_samples = mpeg_bytes_to_samples(read_32bitBE(0xC,streamFile), &mi);
|
||||
vgmstream->num_samples -= vgmstream->num_samples%576;
|
||||
vgmstream->num_samples = mpeg_bytes_to_samples(data_size, &mi);
|
||||
vgmstream->num_samples -= vgmstream->num_samples % frame_size;
|
||||
if (loop_flag) {
|
||||
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 -= vgmstream->loop_end_sample%576;
|
||||
vgmstream->loop_end_sample -= vgmstream->loop_end_sample % frame_size;
|
||||
}
|
||||
vgmstream->interleave_block_size = 0;
|
||||
}
|
||||
|
@ -130,7 +205,6 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_PS3_MSF;
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
|
@ -151,6 +225,12 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) {
|
|||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
if (ffmpeg_data) {
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
vgmstream->codec_data = NULL;
|
||||
}
|
||||
#endif
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1,200 +1,334 @@
|
|||
#include "meta.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;
|
||||
off_t start_offset = 0;
|
||||
STREAMFILE * streamFileSGH = NULL;
|
||||
STREAMFILE * streamHeader = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
char filenameSGH[PATH_LIMIT];
|
||||
int channel_count;
|
||||
int loop_flag;
|
||||
|
||||
off_t start_offset, data_offset;
|
||||
|
||||
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 */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("sgb",filename_extension(filename))) goto fail;
|
||||
|
||||
strcpy(filenameSGH,filename);
|
||||
strcpy(filenameSGH+strlen(filenameSGH)-3,"sgh");
|
||||
|
||||
streamFileSGH = streamFile->open(streamFile,filenameSGH,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!streamFileSGH) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFileSGH) != 0x53475844) /* "SGXD" */
|
||||
is_sgx = strcasecmp("sgx",filename_extension(filename))==0;
|
||||
is_sgd = strcasecmp("sgd",filename_extension(filename))==0;
|
||||
is_sgb = strcasecmp("sgb",filename_extension(filename))==0;
|
||||
if ( !(is_sgx || is_sgd || is_sgb) )
|
||||
goto fail;
|
||||
|
||||
channel_count = read_8bit(0x29,streamFileSGH);
|
||||
if (read_32bitBE(0x44,streamFileSGH)==0xFFFFFFFF)
|
||||
loop_flag = 0;
|
||||
else
|
||||
loop_flag = 1;
|
||||
/* SGB+SGH: use SGH as header; otherwise use the current file as header */
|
||||
if (is_sgb) {
|
||||
char fileheader[PATH_LIMIT];
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
strcpy(fileheader,filename);
|
||||
strcpy(fileheader+strlen(fileheader)-3,"sgh");
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x2C,streamFileSGH);
|
||||
vgmstream->num_samples = read_32bitLE(0xC,streamFileSGH)*28/32;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
if(loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x44,streamFileSGH);
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x48,streamFileSGH);
|
||||
streamHeader = streamFile->open(streamFile,fileheader,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!streamHeader) goto fail;
|
||||
} else {
|
||||
streamHeader = streamFile;
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
if (is_sgx) {
|
||||
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;
|
||||
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;
|
||||
at3_riff_info info;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
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;
|
||||
|
||||
/* 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 */
|
||||
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" */
|
||||
case 0x05: /* todo PCM? */
|
||||
goto fail;
|
||||
|
||||
loop_flag = (read_32bitLE(0x44,streamFile) != 0xFFFFFFFF);
|
||||
channel_count = read_8bit(0x29,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* 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->coding_type = coding_PCM16LE;
|
||||
if (vgmstream->channels > 1) {
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x1;
|
||||
} else {
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x8,streamFile);
|
||||
vgmstream->meta_type = meta_PS3_SGX;
|
||||
break;
|
||||
*/
|
||||
|
||||
#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 */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
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].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset =
|
||||
vgmstream->ch[i].offset =
|
||||
start_offset + vgmstream->interleave_block_size * i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
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);
|
||||
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));
|
||||
if (strcasecmp("sgd",filename_extension(filename))) goto fail;
|
||||
/**
|
||||
* AT3 RIFF headers have a "skip samples at the beginning" value that the decoder should use,
|
||||
* 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 */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x53475844) /* "SGXD" */
|
||||
memset(info, 0, sizeof(at3_riff_info));
|
||||
|
||||
if (read_32bitBE(offset+0x0,streamFile)!=0x52494646 /* "RIFF" */
|
||||
&& read_32bitBE(offset+0x8,streamFile)!=0x57415645 ) /* "WAVE" */
|
||||
goto fail;
|
||||
|
||||
loop_flag = (read_32bitLE(0x44,streamFile) != 0xFFFFFFFF);
|
||||
channel_count = read_8bit(0x29,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
/* read chunks */
|
||||
riff_size = read_32bitLE(offset+0x4,streamFile);
|
||||
current_chunk = offset + 0xc;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = read_32bitLE(0x8,streamFile);
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x2C,streamFile);
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitLE(0x40,streamFile)/16/channel_count*28;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x44,streamFile);
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x48,streamFile);
|
||||
while (!data_found && current_chunk < riff_size) {
|
||||
uint32_t chunk_type = read_32bitBE(current_chunk,streamFile);
|
||||
off_t chunk_size = read_32bitLE(current_chunk+4,streamFile);
|
||||
|
||||
switch(chunk_type) {
|
||||
case 0x736D706C: /* smpl */
|
||||
if (read_32bitLE(current_chunk+0x24, streamFile)==0
|
||||
|| read_32bitLE(current_chunk+0x2c+0x4, streamFile)!=0 )
|
||||
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;
|
||||
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;
|
||||
|
||||
}
|
||||
current_chunk += 8+chunk_size;
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
if (!data_found)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* found */
|
||||
return 1;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
/* not found */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
|
|
@ -220,6 +220,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
off_t file_size = -1;
|
||||
int sample_count = 0;
|
||||
int fact_sample_count = -1;
|
||||
int fact_sample_skip = -1;
|
||||
off_t start_offset = -1;
|
||||
|
||||
int loop_flag = 0;
|
||||
|
@ -241,13 +242,15 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
/* Ubisoft sns */
|
||||
int sns = 0;
|
||||
|
||||
/* Sony atrac3 / 3plus */
|
||||
int at3 = 0;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("wav",filename_extension(filename)) &&
|
||||
strcasecmp("lwav",filename_extension(filename))
|
||||
#if defined(VGM_USE_MAIATRAC3PLUS) || defined(VGM_USE_FFMPEG)
|
||||
&& strcasecmp("at3",filename_extension(filename))
|
||||
&& strcasecmp("sgb",filename_extension(filename))
|
||||
#ifndef VGM_USE_FFMPEG
|
||||
&& strcasecmp("sgb",filename_extension(filename)) /* SGB has proper support with FFmpeg in ps3_sgdx */
|
||||
#endif
|
||||
)
|
||||
{
|
||||
|
@ -255,6 +258,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
mwv = 1;
|
||||
else if (!strcasecmp("sns",filename_extension(filename)))
|
||||
sns = 1;
|
||||
#if defined(VGM_USE_MAIATRAC3PLUS) || defined(VGM_USE_FFMPEG)
|
||||
else if (!strcasecmp("at3",filename_extension(filename)))
|
||||
at3 = 1;
|
||||
#endif
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
@ -348,9 +355,16 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
mwv_ctrl_offset = current_chunk;
|
||||
break;
|
||||
case 0x66616374: /* fact */
|
||||
if (chunk_size != 4
|
||||
&& (!(sns && chunk_size == 0x10))) break;
|
||||
fact_sample_count = read_32bitLE(current_chunk+8, streamFile);
|
||||
if (sns && chunk_size == 0x10) {
|
||||
fact_sample_count = read_32bitLE(current_chunk+0x8, 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;
|
||||
default:
|
||||
/* ignorance is bliss */
|
||||
|
@ -388,13 +402,19 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) );
|
||||
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;
|
||||
#endif
|
||||
#ifdef VGM_USE_MAIATRAC3PLUS
|
||||
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;
|
||||
#endif
|
||||
default:
|
||||
|
@ -565,7 +585,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
|||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
#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
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
|
|
|
@ -271,3 +271,82 @@ size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset,
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
|
||||
#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 {
|
||||
size_t (*read)(struct _STREAMFILE *,uint8_t * dest, off_t offset, size_t length);
|
||||
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,
|
||||
STREAMFILE * infile, int *line_done_ptr);
|
||||
|
||||
int read_key_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -303,7 +303,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_fsb_mpeg,
|
||||
init_vgmstream_nub_vag,
|
||||
init_vgmstream_ps3_past,
|
||||
init_vgmstream_ps3_sgh_sgb,
|
||||
init_vgmstream_ps3_sgdx,
|
||||
init_vgmstream_ngca,
|
||||
init_vgmstream_wii_ras,
|
||||
init_vgmstream_ps2_spm,
|
||||
|
@ -320,11 +320,9 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_eb_sfx,
|
||||
init_vgmstream_eb_sf0,
|
||||
init_vgmstream_ps3_klbs,
|
||||
init_vgmstream_ps3_sgx,
|
||||
init_vgmstream_ps2_mtaf,
|
||||
init_vgmstream_tun,
|
||||
init_vgmstream_wpd,
|
||||
init_vgmstream_ps3_sgd,
|
||||
init_vgmstream_mn_str,
|
||||
init_vgmstream_ps2_mss,
|
||||
init_vgmstream_ps2_hsf,
|
||||
|
@ -340,6 +338,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_g1l,
|
||||
init_vgmstream_hca,
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
init_vgmstream_mp4_aac_ffmpeg,
|
||||
init_vgmstream_ffmpeg,
|
||||
#endif
|
||||
};
|
||||
|
@ -361,7 +360,7 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) {
|
|||
|
||||
/* fail if there is nothing to play
|
||||
* (without this check vgmstream can generate empty files) */
|
||||
if ( vgmstream->num_samples==0 ) {
|
||||
if (vgmstream->num_samples <= 0) {
|
||||
close_vgmstream(vgmstream);
|
||||
continue;
|
||||
}
|
||||
|
@ -514,20 +513,7 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
|
|||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
if (vgmstream->coding_type==coding_FFmpeg) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
|
||||
|
||||
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;
|
||||
reset_ffmpeg(vgmstream);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1083,9 +1069,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
|||
case coding_FFmpeg:
|
||||
{
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
|
||||
if (vgmstream->codec_data) {
|
||||
int64_t samplesRemain = data->totalFrames - data->framesRead;
|
||||
return samplesRemain > 2048 ? 2048 : samplesRemain;
|
||||
if (data) {
|
||||
/* must know the full block size for edge loops */
|
||||
return data->samplesPerBlock;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1788,41 +1774,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
|
|||
}
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
if (vgmstream->coding_type==coding_FFmpeg) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *)(vgmstream->codec_data);
|
||||
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;
|
||||
seek_ffmpeg(vgmstream, vgmstream->loop_start_sample);
|
||||
}
|
||||
#endif /* VGM_USE_FFMPEG */
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
|
@ -1909,7 +1861,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
|
|||
return;
|
||||
}
|
||||
|
||||
snprintf(temp,TEMPSIZE,"sample rate %d Hz\n"
|
||||
snprintf(temp,TEMPSIZE,"sample rate: %d Hz\n"
|
||||
"channels: %d\n",
|
||||
vgmstream->sample_rate,vgmstream->channels);
|
||||
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;
|
||||
if (vgmstream->codec_data) {
|
||||
if (data->codec) {
|
||||
if (data->codec && 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 {
|
||||
snprintf(temp,TEMPSIZE,"FFmpeg");
|
||||
snprintf(temp,TEMPSIZE,"FFmpeg (unknown codec)");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -3190,8 +3145,8 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
|
|||
case meta_PS3_PAST:
|
||||
snprintf(temp,TEMPSIZE,"SNDP header");
|
||||
break;
|
||||
case meta_PS3_SGH_SGB:
|
||||
snprintf(temp,TEMPSIZE,"SGH+SGB SGXD header");
|
||||
case meta_PS3_SGDX:
|
||||
snprintf(temp,TEMPSIZE,"SGXD header");
|
||||
break;
|
||||
case meta_NGCA:
|
||||
snprintf(temp,TEMPSIZE,"NGCA header");
|
||||
|
@ -3244,9 +3199,6 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
|
|||
case meta_PS3_KLBS:
|
||||
snprintf(temp,TEMPSIZE,"klBS Header");
|
||||
break;
|
||||
case meta_PS3_SGX:
|
||||
snprintf(temp,TEMPSIZE,"PS3 SGXD/WAVE header");
|
||||
break;
|
||||
case meta_PS2_MTAF:
|
||||
snprintf(temp,TEMPSIZE,"Konami MTAF header");
|
||||
break;
|
||||
|
|
|
@ -13,7 +13,12 @@ enum { PATH_LIMIT = 32768 };
|
|||
* removing these defines (and the references to the libraries in the
|
||||
* Makefile) */
|
||||
#define VGM_USE_VORBIS
|
||||
|
||||
/* can be disabled to decode with FFmpeg instead */
|
||||
#ifndef VGM_DISABLE_MPEG
|
||||
#define VGM_USE_MPEG
|
||||
#endif
|
||||
|
||||
/* disabled by default, defined for builds that support it */
|
||||
#define VGM_USE_G7221
|
||||
#define VGM_USE_G719
|
||||
|
@ -570,7 +575,7 @@ typedef enum {
|
|||
meta_PS3_MSF, /* MSF header */
|
||||
meta_NUB_VAG, /* VAG from Nub archives */
|
||||
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_WII_RAS, /* Donkey Kong Country Returns (Wii) */
|
||||
meta_PS2_SPM, /* Lethal Skies Elite Pilot: Team SW */
|
||||
|
@ -588,7 +593,6 @@ typedef enum {
|
|||
meta_EB_SFX, // Excitebots .sfx
|
||||
meta_EB_SF0, // Excitebots .sf0
|
||||
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_VAG1, // Metal Gear Solid 3 VAG1
|
||||
meta_PS2_VAG2, // Metal Gear Solid 3 VAG2
|
||||
|
@ -868,6 +872,7 @@ typedef struct {
|
|||
|
||||
// inserted header, ie. fake RIFF header
|
||||
uint8_t *header_insert_block;
|
||||
// header/fake RIFF over the real (parseable by FFmpeg) file start
|
||||
uint64_t header_size;
|
||||
|
||||
// stream info
|
||||
|
@ -875,12 +880,13 @@ typedef struct {
|
|||
int bitsPerSample;
|
||||
int floatingPoint;
|
||||
int sampleRate;
|
||||
int64_t totalFrames;
|
||||
int64_t framesRead;
|
||||
int64_t totalFrames; // sample count, or 0 if unknown
|
||||
int bitrate;
|
||||
|
||||
// Intermediate buffer
|
||||
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
|
||||
AVCodec *codec;
|
||||
|
|
|
@ -234,7 +234,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) {
|
|||
|
||||
+ (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
|
||||
|
|
Loading…
Reference in New Issue