diff --git a/readme.txt b/readme.txt index 417d3a2f..eedee0f0 100644 --- a/readme.txt +++ b/readme.txt @@ -217,6 +217,7 @@ etc: - .afc (GC AFC ADPCM) - .ahx (MPEG-2 Layer II) - .aix (CRI ADX ADPCM) +- .caf (Apple IMA4 ADPCM) - .bgw (FFXI PS-like ADPCM) - .de2 (MS ADPCM) - .kcey (EACS IMA ADPCM) diff --git a/src/Makefile b/src/Makefile index e2d6c3cb..284a1ac8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -203,7 +203,8 @@ META_OBJS=meta/adx_header.o \ meta/ps2_vgv.o \ meta/ngc_gcub.o \ meta/maxis_xa.o \ - meta/ngc_sck_dsp.o + meta/ngc_sck_dsp.o \ + meta/apple_caff.o OBJECTS=vgmstream.o streamfile.o util.o $(CODING_OBJS) $(LAYOUT_OBJS) $(META_OBJS) diff --git a/src/coding/coding.h b/src/coding/coding.h index 5f1b55fb..d27a3fd0 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -21,6 +21,9 @@ void decode_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); +void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); + +void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); void decode_ngc_afc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index 14ae501f..ecfa6c69 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -394,3 +394,53 @@ void decode_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, stream->adpcm_history1_32=hist1; stream->adpcm_step_index=step_index; } + +void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + int i; + + int32_t sample_count=0; + int16_t hist1=stream->adpcm_history1_16; + int step_index = stream->adpcm_step_index; + + off_t packet_offset = stream->offset + first_sample/64*34; + + first_sample = first_sample % 64; + + if (first_sample == 0) + { + hist1 = (int16_t)((uint16_t)read_16bitBE(packet_offset,stream->streamfile) & 0xff80); + step_index = read_8bit(packet_offset+1,stream->streamfile) & 0x7f; + } + + for (i=first_sample,sample_count=0; istreamfile); + sample_nibble = (sample_byte >> (i&1?4:0))&0xf; + + sample_decoded = hist1; + delta = step >> 3; + if (sample_nibble & 1) delta += step >> 2; + if (sample_nibble & 2) delta += step >> 1; + if (sample_nibble & 4) delta += step; + if (sample_nibble & 8) + sample_decoded -= delta; + else + sample_decoded += delta; + + hist1=clamp16(sample_decoded); + + step_index += IMA_IndexTable[sample_nibble&0x7]; + if (step_index < 0) step_index=0; + if (step_index > 88) step_index=88; + + outbuf[sample_count]=(short)(hist1); + } + + stream->adpcm_history1_16=hist1; + stream->adpcm_step_index=step_index; +} diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 51e927ee..bad1f0cc 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -236,6 +236,10 @@ RelativePath=".\meta\aix.c" > + + diff --git a/src/meta/Makefile.unix.am b/src/meta/Makefile.unix.am index 6968c015..859d39a2 100644 --- a/src/meta/Makefile.unix.am +++ b/src/meta/Makefile.unix.am @@ -163,5 +163,6 @@ libmeta_la_SOURCES += ps2_vgv.c libmeta_la_SOURCES += ngc_gcub.c libmeta_la_SOURCES += maxis_xa.c libmeta_la_SOURCES += ngc_sck_dsp.c +libmeta_la_SOURCES += apple_caff.c EXTRA_DIST = meta.h diff --git a/src/meta/apple_caff.c b/src/meta/apple_caff.c new file mode 100644 index 00000000..6624babc --- /dev/null +++ b/src/meta/apple_caff.c @@ -0,0 +1,154 @@ +#include "meta.h" +#include "../util.h" + +/* Apple Core Audio Format */ + +VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + char filename[260]; + + off_t start_offset; + off_t data_size; + off_t sample_count; + off_t interleave; + int sample_rate,unused_frames; + int channel_count; + + /* check extension, case insensitive */ + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strcasecmp("caf",filename_extension(filename))) goto fail; + + /* check "caff" id */ + if (read_32bitBE(0,streamFile)!=0x63616666) goto fail; + /* check version, flags */ + if (read_32bitBE(4,streamFile)!=0x00010000) goto fail; + + off_t chunk_offset = 8; + off_t file_length = (off_t)get_streamfile_size(streamFile); + + int found_desc = 0, found_pakt = 0, found_data = 0; + + while (chunk_offset < file_length) + { + /* high half of size (expect 0s) */ + if (read_32bitBE(chunk_offset+4,streamFile) != 0) goto fail; + + /* handle chunk type */ + switch (read_32bitBE(chunk_offset,streamFile)) + { + case 0x64657363: /* desc */ + found_desc = 1; + { + /* rather than put a convoluted conversion here for + portability, just look it up */ + uint32_t sratefloat = read_32bitBE(chunk_offset+0x0c, streamFile); + if (read_32bitBE(chunk_offset+0x10, streamFile) != 0) goto fail; + switch (sratefloat) + { + case 0x40E58880: + sample_rate = 44100; + break; + default: + goto fail; + } + } + + { + uint32_t codec_4cc = read_32bitBE(chunk_offset+0x14, streamFile); + /* only supporting ima4 for now */ + if (codec_4cc != 0x696d6134) goto fail; + + /* format flags */ + if (read_32bitBE(chunk_offset+0x18, streamFile) != 0) goto fail; + uint32_t bytes_per_packet = read_32bitBE(chunk_offset+0x1c, streamFile); + uint32_t frames_per_packet = read_32bitBE(chunk_offset+0x20, streamFile); + uint32_t channels_per_frame = read_32bitBE(chunk_offset+0x24, streamFile); + uint32_t bits_per_channel = read_32bitBE(chunk_offset+0x28, streamFile); + + interleave = bytes_per_packet / channels_per_frame; + channel_count = channels_per_frame; + if (channels_per_frame != 1 && channels_per_frame != 2) + goto fail; + /* ima4-specific */ + if (frames_per_packet != 64) goto fail; + if ((frames_per_packet / 2 + 2) * channels_per_frame != + bytes_per_packet) goto fail; + if (bits_per_channel != 0) goto fail; + } + break; + case 0x70616b74: /* pakt */ + found_pakt = 1; + /* 64-bit packet table size, 0 for constant bitrate */ + if ( + read_32bitBE(chunk_offset+0x0c,streamFile) != 0 || + read_32bitBE(chunk_offset+0x10,streamFile) != 0) goto fail; + /* high half of valid frames count */ + if (read_32bitBE(chunk_offset+0x14,streamFile) != 0) goto fail; + /* frame count */ + sample_count = read_32bitBE(chunk_offset+0x18,streamFile); + /* priming frames */ + if (read_32bitBE(chunk_offset+0x1c,streamFile) != 0) goto fail; + /* remainder (unused) frames */ + unused_frames = read_32bitBE(chunk_offset+0x20,streamFile); + break; + case 0x66726565: /* free */ + /* padding, ignore */ + break; + case 0x64617461: /* data */ + if (read_32bitBE(chunk_offset+12,streamFile) != 1) goto fail; + found_data = 1; + start_offset = chunk_offset + 16; + data_size = read_32bitBE(chunk_offset+8,streamFile) - 4; + break; + default: + goto fail; + } + + /* done with chunk */ + chunk_offset += 12 + read_32bitBE(chunk_offset+8,streamFile); + } + + if (!found_pakt || !found_desc || !found_data) goto fail; + + /* ima4-specific */ + /* check for full packets */ + if (data_size % (interleave*channel_count) != 0) goto fail; + if ((sample_count+unused_frames)%((interleave-2)*2) != 0) goto fail; + /* check that all packets are accounted for */ + if (data_size/interleave/channel_count != + (sample_count+unused_frames)/((interleave-2)*2)) goto fail; + + vgmstream = allocate_vgmstream(channel_count,0); + if (!vgmstream) goto fail; + + vgmstream->channels = channel_count; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = sample_count; + /* ima4-specific */ + vgmstream->coding_type = coding_APPLE_IMA4; + if (channel_count == 2) + vgmstream->layout_type = layout_interleave; + else + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = interleave; + vgmstream->meta_type = meta_CAFF; + + /* open the file for reading by each channel */ + { + int i; + for (i=0;ich[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!vgmstream->ch[i].streamfile) goto fail; + + vgmstream->ch[i].offset = + vgmstream->ch[i].channel_start_offset = + start_offset + interleave * i; + } + } + + return vgmstream; +fail: + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/meta.h b/src/meta/meta.h index 23ad6a62..2fa98ea0 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -405,4 +405,6 @@ VGMSTREAM * init_vgmstream_maxis_xa(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ngc_sck_dsp(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE * streamFile); + #endif diff --git a/src/vgmstream.c b/src/vgmstream.c index b6fa668d..f0ea52e6 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -222,6 +222,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_gcub, init_vgmstream_maxis_xa, init_vgmstream_ngc_sck_dsp, + init_vgmstream_apple_caff, }; #define INIT_VGMSTREAM_FCNS (sizeof(init_vgmstream_fcns)/sizeof(init_vgmstream_fcns[0])) @@ -715,6 +716,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return vgmstream->ws_output_size; case coding_MSADPCM: return (vgmstream->interleave_block_size-(7-1)*vgmstream->channels)*2/vgmstream->channels; + case coding_APPLE_IMA4: + return 64; case coding_MS_IMA: return (vgmstream->interleave_block_size-4*vgmstream->channels)*2/vgmstream->channels; case coding_NDS_PROCYON: @@ -793,6 +796,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_INT_DVI_IMA: case coding_AICA: return 1; + case coding_APPLE_IMA4: + return 34; case coding_MSADPCM: return vgmstream->interleave_block_size; default: @@ -1048,6 +1053,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to samples_to_do); } break; + case coding_APPLE_IMA4: + for (chan=0;chanchannels;chan++) { + decode_apple_ima4(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan, + vgmstream->channels,vgmstream->samples_into_block, + samples_to_do); + } + break; case coding_WS: for (chan=0;chanchannels;chan++) { decode_ws(vgmstream,chan,buffer+samples_written*vgmstream->channels+chan, @@ -1389,6 +1401,9 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { case coding_MS_IMA: snprintf(temp,TEMPSIZE,"Microsoft 4-bit IMA ADPCM"); break; + case coding_APPLE_IMA4: + snprintf(temp,TEMPSIZE,"Apple Quicktime 4-bit IMA ADPCM"); + break; case coding_WS: snprintf(temp,TEMPSIZE,"Westwood Studios DPCM"); break; @@ -2234,6 +2249,9 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { case meta_NGC_SWD: snprintf(temp,TEMPSIZE,"PSF + Standard DSP Headers"); break; + case meta_CAFF: + snprintf(temp,TEMPSIZE,"Apple Core Audio Format Header"); + break; default: snprintf(temp,TEMPSIZE,"THEY SHOULD HAVE SENT A POET"); } diff --git a/src/vgmstream.h b/src/vgmstream.h index 57537681..f3761ec0 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -73,6 +73,7 @@ typedef enum { coding_IMA, /* bare IMA, low nibble first */ coding_INT_IMA, /* */ coding_MS_IMA, /* Microsoft IMA */ + coding_APPLE_IMA4, /* Apple Quicktime IMA4 */ coding_WS, /* Westwood Studios' custom VBR ADPCM */ #ifdef VGM_USE_MPEG coding_fake_MPEG2_L2, /* MPEG-2 Layer 2 (AHX), with lying headers */ @@ -404,6 +405,7 @@ typedef enum { meta_NGC_GCUB, /* Sega Soccer Slam */ meta_MAXIS_XA, /* Sim City 3000 (PC) */ meta_NGC_SCK_DSP, /* Scorpion King (NGC) */ + meta_CAFF, /* iPhone .caf */ } meta_t; typedef struct { @@ -647,4 +649,4 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length); * stereo stream. */ void try_dual_file_stereo(VGMSTREAM * opened_stream, STREAMFILE *streamFile); -#endif \ No newline at end of file +#endif diff --git a/unix/data.c b/unix/data.c index 191f9f14..8cfce0c6 100644 --- a/unix/data.c +++ b/unix/data.c @@ -182,6 +182,7 @@ gchar *vgmstream_exts [] = { "2dx", "adpcm", "hwas", + "caf", /* terminator */ NULL }; diff --git a/winamp/in_vgmstream.c b/winamp/in_vgmstream.c index 79dcc1b0..46328904 100644 --- a/winamp/in_vgmstream.c +++ b/winamp/in_vgmstream.c @@ -110,6 +110,7 @@ char * extension_list[] = { "bmdx\0BMDX Audio File (*.BMDX)\0", "brstm;brstmspm\0BRSTM Audio File (*.BRSTM)\0", + "caf\0CAF Audio File (*.CAF)\0", "ccc\0CCC Audio File (*.CCC)\0", "cfn\0CFN Audio File (*.CFN)\0", "cnk\0CNK Audio File (*.CNK)\0",