mirror of
https://github.com/Shpoike/Quakespasm.git
synced 2024-11-10 07:21:58 +00:00
snd_mp3.c: moved mp3 tag stuff into a separate module snd_mp3tag.c
so that it can be used in snd_mpg123.c, too, if needed. git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@1658 af15c1b1-3010-417e-b628-4374ebc0bcbd
This commit is contained in:
parent
b2bf251a32
commit
ffe43cf024
16 changed files with 391 additions and 208 deletions
|
@ -266,6 +266,9 @@
|
|||
<Unit filename="../../Quake/snd_mp3.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="../../Quake/snd_mp3tag.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="../../Quake/snd_mp3.h" />
|
||||
<Unit filename="../../Quake/snd_opus.c">
|
||||
<Option compilerVar="CC" />
|
||||
|
|
|
@ -265,6 +265,9 @@
|
|||
<Unit filename="../../Quake/snd_mp3.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="../../Quake/snd_mp3tag.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="../../Quake/snd_mp3.h" />
|
||||
<Unit filename="../../Quake/snd_opus.c">
|
||||
<Option compilerVar="CC" />
|
||||
|
|
|
@ -105,6 +105,8 @@
|
|||
48E2EC8815FB516600B8D476 /* libvorbis.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7B15FB507A00B8D476 /* libvorbis.dylib */; };
|
||||
48E2EC8915FB516600B8D476 /* libvorbisfile.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */; };
|
||||
48FE585B0D3A82C8006BB491 /* QuakeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */; };
|
||||
63D6EB3523A255900047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB3423A255900047028C /* snd_mp3tag.c */; };
|
||||
63D6EB3623A255900047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB3423A255900047028C /* snd_mp3tag.c */; };
|
||||
664D988A19CF6B78000D395C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
|
||||
664D988B19CF6B78000D395C /* Launcher.nib in Resources */ = {isa = PBXBuildFile; fileRef = 48B9E7860D340B1E0001CACF /* Launcher.nib */; };
|
||||
664D988C19CF6B78000D395C /* QuakeSpasm.icns in Resources */ = {isa = PBXBuildFile; fileRef = 484AA4B30D3FF6C0005D917A /* QuakeSpasm.icns */; };
|
||||
|
@ -470,6 +472,7 @@
|
|||
48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libvorbisfile.dylib; path = codecs/lib/libvorbisfile.dylib; sourceTree = "<group>"; };
|
||||
48FE58590D3A82C8006BB491 /* QuakeArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuakeArguments.h; sourceTree = "<group>"; };
|
||||
48FE585A0D3A82C8006BB491 /* QuakeArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QuakeArguments.m; sourceTree = "<group>"; };
|
||||
63D6EB3423A255900047028C /* snd_mp3tag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_mp3tag.c; path = ../Quake/snd_mp3tag.c; sourceTree = SOURCE_ROOT; };
|
||||
664D98F919CF6B78000D395C /* QuakeSpasm-SDL2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "QuakeSpasm-SDL2.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
664D98FB19CF6B78000D395C /* Info copy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info copy.plist"; sourceTree = "<group>"; };
|
||||
664D990519CF6E16000D395C /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -716,6 +719,7 @@
|
|||
486577CA0D31A22A00E7920A /* snd_mix.c */,
|
||||
483A78540D2EEAC300CB2E4C /* snd_sdl.c */,
|
||||
4854B1B01340C646004C9F45 /* snd_mp3.c */,
|
||||
63D6EB3423A255900047028C /* snd_mp3tag.c */,
|
||||
4885A84A179740A0000EC703 /* snd_opus.c */,
|
||||
483A78640D2EEAF000CB2E4C /* snd_mikmod.c */,
|
||||
4818B0B012D5BA1A006DD66E /* snd_umx.c */,
|
||||
|
@ -1029,6 +1033,7 @@
|
|||
664D98DA19CF6B78000D395C /* strlcpy.c in Sources */,
|
||||
664D98DB19CF6B78000D395C /* snd_opus.c in Sources */,
|
||||
664D98DC19CF6B78000D395C /* snd_flac.c in Sources */,
|
||||
63D6EB3623A255900047028C /* snd_mp3tag.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1115,6 +1120,7 @@
|
|||
48A7C1FD14AA34940011B754 /* strlcpy.c in Sources */,
|
||||
4885A84C179740A0000EC703 /* snd_opus.c in Sources */,
|
||||
48281301179C3F13004E1D61 /* snd_flac.c in Sources */,
|
||||
63D6EB3523A255900047028C /* snd_mp3tag.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
48E2EC8815FB516600B8D476 /* libvorbis.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7B15FB507A00B8D476 /* libvorbis.dylib */; };
|
||||
48E2EC8915FB516600B8D476 /* libvorbisfile.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */; };
|
||||
48FE585B0D3A82C8006BB491 /* QuakeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */; };
|
||||
63D6EB5F23A2563B0047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB5E23A2563B0047028C /* snd_mp3tag.c */; };
|
||||
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
|
||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||
A4E3AF2A05D43FCC000F1B47 /* libmikmod.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = F5F5DE80017CB4370103A810 /* libmikmod.dylib */; };
|
||||
|
@ -301,6 +302,7 @@
|
|||
48FE58590D3A82C8006BB491 /* QuakeArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuakeArguments.h; sourceTree = "<group>"; };
|
||||
48FE585A0D3A82C8006BB491 /* QuakeArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QuakeArguments.m; sourceTree = "<group>"; };
|
||||
664D98FB19CF6B78000D395C /* Info copy.plist */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Info copy.plist"; sourceTree = "<group>"; };
|
||||
63D6EB5E23A2563B0047028C /* snd_mp3tag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_mp3tag.c; path = ../Quake/snd_mp3tag.c; sourceTree = SOURCE_ROOT; };
|
||||
664D990519CF6E16000D395C /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = SOURCE_ROOT; };
|
||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
8D1107320486CEB800E47090 /* QuakeSpasm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuakeSpasm.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -519,6 +521,7 @@
|
|||
486577CA0D31A22A00E7920A /* snd_mix.c */,
|
||||
483A78540D2EEAC300CB2E4C /* snd_sdl.c */,
|
||||
4854B1B01340C646004C9F45 /* snd_mp3.c */,
|
||||
63D6EB5E23A2563B0047028C /* snd_mp3tag.c */,
|
||||
4885A84A179740A0000EC703 /* snd_opus.c */,
|
||||
483A78640D2EEAF000CB2E4C /* snd_mikmod.c */,
|
||||
4818B0B012D5BA1A006DD66E /* snd_umx.c */,
|
||||
|
@ -786,6 +789,7 @@
|
|||
48A7C1FD14AA34940011B754 /* strlcpy.c in Sources */,
|
||||
4885A84C179740A0000EC703 /* snd_opus.c in Sources */,
|
||||
48281301179C3F13004E1D61 /* snd_flac.c in Sources */,
|
||||
63D6EB5F23A2563B0047028C /* snd_mp3tag.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -124,11 +124,11 @@ $(error Invalid MP3LIB setting)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(MP3LIB),mad)
|
||||
mp3_obj=snd_mp3.o
|
||||
mp3_obj=snd_mp3
|
||||
lib_mp3dec=-lmad
|
||||
endif
|
||||
ifeq ($(MP3LIB),mpg123)
|
||||
mp3_obj=snd_mpg123.o
|
||||
mp3_obj=snd_mpg123
|
||||
lib_mp3dec=-lmpg123
|
||||
endif
|
||||
ifeq ($(VORBISLIB),vorbis)
|
||||
|
@ -205,7 +205,8 @@ MUSIC_OBJS:= bgmusic.o \
|
|||
snd_wave.o \
|
||||
snd_vorbis.o \
|
||||
snd_opus.o \
|
||||
$(mp3_obj) \
|
||||
$(mp3_obj).o \
|
||||
snd_mp3tag.o \
|
||||
snd_mikmod.o \
|
||||
snd_xmp.o \
|
||||
snd_umx.o
|
||||
|
|
|
@ -137,11 +137,11 @@ $(error Invalid MP3LIB setting)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(MP3LIB),mad)
|
||||
mp3_obj=snd_mp3.o
|
||||
mp3_obj=snd_mp3
|
||||
lib_mp3dec=-lmad
|
||||
endif
|
||||
ifeq ($(MP3LIB),mpg123)
|
||||
mp3_obj=snd_mpg123.o
|
||||
mp3_obj=snd_mpg123
|
||||
lib_mp3dec=-lmpg123
|
||||
endif
|
||||
ifeq ($(VORBISLIB),vorbis)
|
||||
|
@ -231,7 +231,8 @@ MUSIC_OBJS:= bgmusic.o \
|
|||
snd_wave.o \
|
||||
snd_vorbis.o \
|
||||
snd_opus.o \
|
||||
$(mp3_obj) \
|
||||
$(mp3_obj).o \
|
||||
snd_mp3tag.o \
|
||||
snd_mikmod.o \
|
||||
snd_xmp.o \
|
||||
snd_umx.o
|
||||
|
|
|
@ -103,11 +103,11 @@ $(error Invalid MP3LIB setting)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(MP3LIB),mad)
|
||||
mp3_obj=snd_mp3.o
|
||||
mp3_obj=snd_mp3
|
||||
lib_mp3dec=-lmad
|
||||
endif
|
||||
ifeq ($(MP3LIB),mpg123)
|
||||
mp3_obj=snd_mpg123.o
|
||||
mp3_obj=snd_mpg123
|
||||
lib_mp3dec=-lmpg123
|
||||
endif
|
||||
ifeq ($(VORBISLIB),vorbis)
|
||||
|
@ -195,7 +195,8 @@ MUSIC_OBJS:= bgmusic.o \
|
|||
snd_wave.o \
|
||||
snd_vorbis.o \
|
||||
snd_opus.o \
|
||||
$(mp3_obj) \
|
||||
$(mp3_obj).o \
|
||||
snd_mp3tag.o \
|
||||
snd_mikmod.o \
|
||||
snd_xmp.o \
|
||||
snd_umx.o
|
||||
|
|
|
@ -101,11 +101,11 @@ $(error Invalid MP3LIB setting)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(MP3LIB),mad)
|
||||
mp3_obj=snd_mp3.o
|
||||
mp3_obj=snd_mp3
|
||||
lib_mp3dec=-lmad
|
||||
endif
|
||||
ifeq ($(MP3LIB),mpg123)
|
||||
mp3_obj=snd_mpg123.o
|
||||
mp3_obj=snd_mpg123
|
||||
lib_mp3dec=-lmpg123
|
||||
endif
|
||||
ifeq ($(VORBISLIB),vorbis)
|
||||
|
@ -193,7 +193,8 @@ MUSIC_OBJS:= bgmusic.o \
|
|||
snd_wave.o \
|
||||
snd_vorbis.o \
|
||||
snd_opus.o \
|
||||
$(mp3_obj) \
|
||||
$(mp3_obj).o \
|
||||
snd_mp3tag.o \
|
||||
snd_mikmod.o \
|
||||
snd_xmp.o \
|
||||
snd_umx.o
|
||||
|
|
|
@ -53,11 +53,11 @@ NET_LIBS = $(LIBWINSOCK)
|
|||
CODEC_INC = -I../Windows/codecs/include
|
||||
LIBCODEC = ../Windows/codecs/x86-watcom/
|
||||
!ifeq MP3LIB mad
|
||||
mp3_obj=snd_mp3.obj
|
||||
mp3_obj=snd_mp3
|
||||
lib_mp3dec=$(LIBCODEC)mad.lib
|
||||
!endif
|
||||
!ifeq MP3LIB mpg123
|
||||
mp3_obj=snd_mpg123.obj
|
||||
mp3_obj=snd_mpg123
|
||||
lib_mp3dec=$(LIBCODEC)mpg123.lib
|
||||
!endif
|
||||
!ifeq VORBISLIB vorbis
|
||||
|
@ -140,7 +140,8 @@ MUSIC_OBJS= bgmusic.obj &
|
|||
snd_wave.obj &
|
||||
snd_vorbis.obj &
|
||||
snd_opus.obj &
|
||||
$(mp3_obj) &
|
||||
$(mp3_obj).obj &
|
||||
snd_mp3tag.obj &
|
||||
snd_mikmod.obj &
|
||||
snd_xmp.obj &
|
||||
snd_umx.obj
|
||||
|
|
194
Quake/snd_mp3.c
194
Quake/snd_mp3.c
|
@ -61,198 +61,6 @@ typedef struct _mp3_priv_t
|
|||
} mp3_priv_t;
|
||||
|
||||
|
||||
/* TAG HANDLING:
|
||||
* put together by O.Sezer <sezero@users.sourceforge.net> using public specs,
|
||||
* put into public domain.
|
||||
*/
|
||||
static inline qboolean tag_is_id3v1(const unsigned char *data, size_t length) {
|
||||
/* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */
|
||||
if (length < 3 || memcmp(data,"TAG",3) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline qboolean tag_is_id3v2(const unsigned char *data, size_t length) {
|
||||
/* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */
|
||||
/* bytes 0-2: "ID3" identifier */
|
||||
if (length < 10 || memcmp(data,"ID3",3) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* bytes 3-4: version num (major,revision), each byte always less than 0xff. */
|
||||
if (data[3] == 0xff || data[4] == 0xff) {
|
||||
return false;
|
||||
}
|
||||
/* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the
|
||||
* highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte ->
|
||||
* effectively a 28 bit value. */
|
||||
if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline long get_id3v2_len(const unsigned char *data, long length) {
|
||||
/* size is a 'synchsafe' integer (see above) */
|
||||
long size = (long)((data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]);
|
||||
size += 10; /* header size */
|
||||
/* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero).
|
||||
* bit 4 set: footer is present (a copy of the header but
|
||||
* with "3DI" as ident.) */
|
||||
if (data[5] & 0x10) {
|
||||
size += 10; /* footer size */
|
||||
}
|
||||
/* optional padding (always zeroes) */
|
||||
while (size < length && data[size] == 0) {
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
static inline qboolean tag_is_apetag(const unsigned char *data, size_t length) {
|
||||
/* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification
|
||||
* Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version,
|
||||
* bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */
|
||||
unsigned int v;
|
||||
|
||||
if (length < 32 || memcmp(data,"APETAGEX",8) != 0) {
|
||||
return false;
|
||||
}
|
||||
v = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); /* version */
|
||||
if (v != 2000U && v != 1000U) {
|
||||
return false;
|
||||
}
|
||||
v = 0; /* reserved bits : */
|
||||
if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline long get_ape_len(const unsigned char *data) {
|
||||
unsigned int flags, version;
|
||||
long size = (long)((data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]);
|
||||
version = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]);
|
||||
flags = (unsigned)((data[23]<<24) | (data[22]<<16) | (data[21]<<8) | data[20]);
|
||||
if (version == 2000U && (flags & (1U<<31))) size += 32; /* header present. */
|
||||
return size;
|
||||
}
|
||||
static inline int is_lyrics3tag(const unsigned char *data, size_t length) {
|
||||
/* http://id3.org/Lyrics3
|
||||
* http://id3.org/Lyrics3v2 */
|
||||
if (length < 15) return 0;
|
||||
if (memcmp(data+6,"LYRICS200",9) == 0) return 2; /* v2 */
|
||||
if (memcmp(data+6,"LYRICSEND",9) == 0) return 1; /* v1 */
|
||||
return 0;
|
||||
}
|
||||
static inline long get_lyrics3v1_len(snd_stream_t *stream, unsigned char *buf) {
|
||||
const char *p; long i, len;
|
||||
/* needs manual search: http://id3.org/Lyrics3 */
|
||||
/* this relies on the input_buffer size >= 5100 */
|
||||
if (stream->fh.length < 20) return -1;
|
||||
len = (stream->fh.length > 5109)? 5109 : stream->fh.length;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */
|
||||
/* strstr() won't work here. */
|
||||
for (i = len - 11, p = (const char*)buf; i >= 0; --i, ++p) {
|
||||
if (memcmp(p, "LYRICSBEGIN", 11) == 0)
|
||||
break;
|
||||
}
|
||||
if (i < 0) return -1;
|
||||
return len - (long)(p - (const char*)buf) + 9 /* footer */;
|
||||
}
|
||||
static inline long get_lyrics3v2_len(const unsigned char *data, size_t length) {
|
||||
/* 6 bytes before the end marker is size in decimal format -
|
||||
* does not include the 9 bytes end marker and size field. */
|
||||
if (length != 6) return 0;
|
||||
return strtol((const char *)data, NULL, 10) + 15;
|
||||
}
|
||||
static inline qboolean verify_lyrics3v2(const unsigned char *data, size_t length) {
|
||||
if (length < 11) return false;
|
||||
if (memcmp(data,"LYRICSBEGIN",11) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int mp3_skiptags(snd_stream_t *stream, unsigned char *buf, size_t bufsize)
|
||||
{
|
||||
long len; size_t readsize;
|
||||
int rc = -1;
|
||||
|
||||
readsize = FS_fread(buf, 1, bufsize, &stream->fh);
|
||||
if (!readsize || FS_ferror(&stream->fh)) goto fail;
|
||||
|
||||
/* ID3v2 tag is at the start */
|
||||
if (tag_is_id3v2(buf, readsize)) {
|
||||
len = get_id3v2_len(buf, (long)readsize);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.start += len;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len);
|
||||
}
|
||||
/* APE tag _might_ be at the start (discouraged
|
||||
* but not forbidden, either.) read the header. */
|
||||
else if (tag_is_apetag(buf, readsize)) {
|
||||
len = get_ape_len(buf);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.start += len;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
|
||||
}
|
||||
|
||||
/* ID3v1 tag is at the end */
|
||||
if (stream->fh.length >= 128) {
|
||||
FS_fseek(&stream->fh, -128, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 128, &stream->fh);
|
||||
if (readsize != 128) goto fail;
|
||||
if (tag_is_id3v1(buf, 128)) {
|
||||
stream->fh.length -= 128;
|
||||
Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L);
|
||||
/* FIXME: handle possible double-ID3v1 tags? */
|
||||
}
|
||||
}
|
||||
|
||||
/* do we know whether ape or lyrics3 is the first?
|
||||
* well, we don't: we need to handle that later... */
|
||||
|
||||
/* APE tag may be at the end: read the footer */
|
||||
if (stream->fh.length >= 32) {
|
||||
FS_fseek(&stream->fh, -32, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 32, &stream->fh);
|
||||
if (readsize != 32) goto fail;
|
||||
if (tag_is_apetag(buf, 32)) {
|
||||
len = get_ape_len(buf);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->fh.length >= 15) {
|
||||
FS_fseek(&stream->fh, -15, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 15, &stream->fh);
|
||||
if (readsize != 15) goto fail;
|
||||
len = is_lyrics3tag(buf, 15);
|
||||
if (len == 2) {
|
||||
len = get_lyrics3v2_len(buf, 6);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
if (len < 15) goto fail;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 11, &stream->fh);
|
||||
if (readsize != 11) goto fail;
|
||||
if (!verify_lyrics3v2(buf, 11)) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
|
||||
}
|
||||
else if (len == 1) {
|
||||
len = get_lyrics3v1_len(stream, buf);
|
||||
if (len < 0) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
fail:
|
||||
FS_rewind(&stream->fh);
|
||||
return (stream->fh.length > 0)? rc : -1;
|
||||
}
|
||||
|
||||
/* (Re)fill the stream buffer that is to be decoded. If any data
|
||||
* still exists in the buffer then they are first shifted to be
|
||||
* front of the stream buffer. */
|
||||
|
@ -292,7 +100,7 @@ static int mp3_startread(snd_stream_t *stream)
|
|||
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
|
||||
size_t ReadSize;
|
||||
|
||||
if (mp3_skiptags(stream, p->mp3_buffer, MP3_BUFFER_SIZE) < 0)
|
||||
if (mp3_skiptags(stream) < 0)
|
||||
return -1;
|
||||
|
||||
mad_stream_init(&p->Stream);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#if defined(USE_CODEC_MP3)
|
||||
|
||||
extern snd_codec_t mp3_codec;
|
||||
int mp3_skiptags(snd_stream_t *);
|
||||
|
||||
#endif /* USE_CODEC_MP3 */
|
||||
|
||||
|
|
339
Quake/snd_mp3tag.c
Normal file
339
Quake/snd_mp3tag.c
Normal file
|
@ -0,0 +1,339 @@
|
|||
/* MP3 TAGS STUFF -- put together using public specs.
|
||||
* Copyright (C) 2018-2019 O.Sezer <sezero@users.sourceforge.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "quakedef.h"
|
||||
|
||||
#if defined(USE_CODEC_MP3)
|
||||
#include "snd_codec.h"
|
||||
#include "q_ctype.h"
|
||||
|
||||
static inline qboolean tag_is_id3v1(const unsigned char *data, size_t length) {
|
||||
/* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */
|
||||
if (length < 3 || memcmp(data,"TAG",3) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline qboolean tag_is_id3v2(const unsigned char *data, size_t length) {
|
||||
/* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */
|
||||
/* bytes 0-2: "ID3" identifier */
|
||||
if (length < 10 || memcmp(data,"ID3",3) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* bytes 3-4: version num (major,revision), each byte always less than 0xff. */
|
||||
if (data[3] == 0xff || data[4] == 0xff) {
|
||||
return false;
|
||||
}
|
||||
/* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the
|
||||
* highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte ->
|
||||
* effectively a 28 bit value. */
|
||||
if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline long get_id3v2_len(const unsigned char *data, long length) {
|
||||
/* size is a 'synchsafe' integer (see above) */
|
||||
long size = (long)((data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]);
|
||||
size += 10; /* header size */
|
||||
/* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero).
|
||||
* bit 4 set: footer is present (a copy of the header but
|
||||
* with "3DI" as ident.) */
|
||||
if (data[5] & 0x10) {
|
||||
size += 10; /* footer size */
|
||||
}
|
||||
/* optional padding (always zeroes) */
|
||||
while (size < length && data[size] == 0) {
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
static inline qboolean tag_is_apetag(const unsigned char *data, size_t length) {
|
||||
/* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification
|
||||
* Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version,
|
||||
* bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */
|
||||
unsigned int v;
|
||||
|
||||
if (length < 32 || memcmp(data,"APETAGEX",8) != 0) {
|
||||
return false;
|
||||
}
|
||||
v = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); /* version */
|
||||
if (v != 2000U && v != 1000U) {
|
||||
return false;
|
||||
}
|
||||
v = 0; /* reserved bits : */
|
||||
if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline long get_ape_len(const unsigned char *data) {
|
||||
unsigned int flags, version;
|
||||
long size = (long)((data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]);
|
||||
version = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]);
|
||||
flags = (unsigned)((data[23]<<24) | (data[22]<<16) | (data[21]<<8) | data[20]);
|
||||
if (version == 2000U && (flags & (1U<<31))) size += 32; /* header present. */
|
||||
return size;
|
||||
}
|
||||
static inline int is_lyrics3tag(const unsigned char *data, size_t length) {
|
||||
/* http://id3.org/Lyrics3
|
||||
* http://id3.org/Lyrics3v2 */
|
||||
if (length < 15) return 0;
|
||||
if (memcmp(data+6,"LYRICS200",9) == 0) return 2; /* v2 */
|
||||
if (memcmp(data+6,"LYRICSEND",9) == 0) return 1; /* v1 */
|
||||
return 0;
|
||||
}
|
||||
static inline long get_lyrics3v1_len(snd_stream_t *stream) {
|
||||
const char *p; long i, len;
|
||||
char buf[5104];
|
||||
/* needs manual search: http://id3.org/Lyrics3 */
|
||||
if (stream->fh.length < 20) return -1;
|
||||
len = (stream->fh.length > 5109)? 5109 : stream->fh.length;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */
|
||||
/* strstr() won't work here. */
|
||||
for (i = len - 11, p = buf; i >= 0; --i, ++p) {
|
||||
if (memcmp(p, "LYRICSBEGIN", 11) == 0)
|
||||
break;
|
||||
}
|
||||
if (i < 0) return -1;
|
||||
return len - (long)(p - buf) + 9 /* footer */;
|
||||
}
|
||||
static inline long get_lyrics3v2_len(const unsigned char *data, size_t length) {
|
||||
/* 6 bytes before the end marker is size in decimal format -
|
||||
* does not include the 9 bytes end marker and size field. */
|
||||
if (length != 6) return 0;
|
||||
return strtol((const char *)data, NULL, 10) + 15;
|
||||
}
|
||||
static inline qboolean verify_lyrics3v2(const unsigned char *data, size_t length) {
|
||||
if (length < 11) return false;
|
||||
if (memcmp(data,"LYRICSBEGIN",11) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
#define MMTAG_PARANOID
|
||||
static inline qboolean is_musicmatch(const unsigned char *data, long length) {
|
||||
/* From docs/musicmatch.txt in id3lib: https://sourceforge.net/projects/id3lib/
|
||||
Overall tag structure:
|
||||
|
||||
+-----------------------------+
|
||||
| Header |
|
||||
| (256 bytes, OPTIONAL) |
|
||||
+-----------------------------+
|
||||
| Image extension (4 bytes) |
|
||||
+-----------------------------+
|
||||
| Image binary |
|
||||
| (var. length >= 4 bytes) |
|
||||
+-----------------------------+
|
||||
| Unused (4 bytes) |
|
||||
+-----------------------------+
|
||||
| Version info (256 bytes) |
|
||||
+-----------------------------+
|
||||
| Audio meta-data |
|
||||
| (var. length >= 7868 bytes) |
|
||||
+-----------------------------+
|
||||
| Data offsets (20 bytes) |
|
||||
+-----------------------------+
|
||||
| Footer (48 bytes) |
|
||||
+-----------------------------+
|
||||
*/
|
||||
if (length < 48) return false;
|
||||
/* sig: 19 bytes company name + 13 bytes space */
|
||||
if (memcmp(data,"Brava Software Inc. ",32) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* 4 bytes version: x.xx */
|
||||
if (!q_isdigit(data[32]) || data[33] != '.' ||
|
||||
!q_isdigit(data[34]) ||!q_isdigit(data[35])) {
|
||||
return false;
|
||||
}
|
||||
#ifdef MMTAG_PARANOID
|
||||
/* [36..47]: 12 bytes trailing space */
|
||||
for (length = 36; length < 48; ++length) {
|
||||
if (data[length] != ' ') return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
static inline long get_musicmatch_len(snd_stream_t *stream) {
|
||||
const int metasizes[4] = { 7868, 7936, 8004, 8132 };
|
||||
const unsigned char syncstr[10] = {'1','8','2','7','3','6','4','5',0,0};
|
||||
unsigned char buf[256];
|
||||
int i, j, imgext_ofs, version_ofs;
|
||||
long len;
|
||||
|
||||
FS_fseek(&stream->fh, -68, SEEK_END);
|
||||
FS_fread(buf, 1, 20, &stream->fh);
|
||||
imgext_ofs = (int)((buf[3] <<24) | (buf[2] <<16) | (buf[1] <<8) | buf[0] );
|
||||
version_ofs = (int)((buf[15]<<24) | (buf[14]<<16) | (buf[13]<<8) | buf[12]);
|
||||
if (version_ofs <= imgext_ofs) return -1;
|
||||
if (version_ofs <= 0 || imgext_ofs <= 0) return -1;
|
||||
/* Try finding the version info section:
|
||||
* Because metadata section comes after it, and because metadata section
|
||||
* has different sizes across versions (format ver. <= 3.00: always 7868
|
||||
* bytes), we can _not_ directly calculate using deltas from the offsets
|
||||
* section. */
|
||||
for (i = 0; i < 4; ++i) {
|
||||
/* 48: footer, 20: offsets, 256: version info */
|
||||
len = metasizes[i] + 48 + 20 + 256;
|
||||
if (stream->fh.length < len) return -1;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
FS_fread(buf, 1, 256, &stream->fh);
|
||||
/* [0..9]: sync string, [30..255]: 0x20 */
|
||||
#ifdef MMTAG_PARANOID
|
||||
for (j = 30; j < 256; ++j) {
|
||||
if (buf[j] != ' ') break;
|
||||
}
|
||||
if (j < 256) continue;
|
||||
#endif
|
||||
if (memcmp(buf, syncstr, 10) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 4) return -1; /* no luck. */
|
||||
#ifdef MMTAG_PARANOID
|
||||
/* unused section: (4 bytes of 0x00) */
|
||||
FS_fseek(&stream->fh, -(len + 4), SEEK_END);
|
||||
FS_fread(buf, 1, 4, &stream->fh); j = 0;
|
||||
if (memcmp(buf, &j, 4) != 0) return -1;
|
||||
#endif
|
||||
len += (version_ofs - imgext_ofs);
|
||||
if (stream->fh.length < len) return -1;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
FS_fread(buf, 1, 8, &stream->fh);
|
||||
j = (int)((buf[7] <<24) | (buf[6] <<16) | (buf[5] <<8) | buf[4]);
|
||||
if (j < 0) return -1;
|
||||
/* verify image size: */
|
||||
/* without this, we may land at a wrong place. */
|
||||
if (j + 12 != version_ofs - imgext_ofs) return -1;
|
||||
/* try finding the optional header */
|
||||
if (stream->fh.length < len + 256) return len;
|
||||
FS_fseek(&stream->fh, -(len + 256), SEEK_END);
|
||||
FS_fread(buf, 1, 256, &stream->fh);
|
||||
/* [0..9]: sync string, [30..255]: 0x20 */
|
||||
if (memcmp(buf, syncstr, 10) != 0) {
|
||||
return len;
|
||||
}
|
||||
#ifdef MMTAG_PARANOID
|
||||
for (j = 30; j < 256; ++j) {
|
||||
if (buf[j] != ' ') return len;
|
||||
}
|
||||
#endif
|
||||
return len + 256; /* header is present. */
|
||||
}
|
||||
|
||||
int mp3_skiptags(snd_stream_t *stream)
|
||||
{
|
||||
unsigned char buf[128];
|
||||
long len; size_t readsize;
|
||||
int rc = -1;
|
||||
|
||||
readsize = FS_fread(buf, 1, 128, &stream->fh);
|
||||
if (!readsize || FS_ferror(&stream->fh)) goto fail;
|
||||
|
||||
/* ID3v2 tag is at the start */
|
||||
if (tag_is_id3v2(buf, readsize)) {
|
||||
len = get_id3v2_len(buf, (long)readsize);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.start += len;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len);
|
||||
}
|
||||
/* APE tag _might_ be at the start (discouraged
|
||||
* but not forbidden, either.) read the header. */
|
||||
else if (tag_is_apetag(buf, readsize)) {
|
||||
len = get_ape_len(buf);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.start += len;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
|
||||
}
|
||||
|
||||
/* ID3v1 tag is at the end */
|
||||
if (stream->fh.length >= 128) {
|
||||
FS_fseek(&stream->fh, -128, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 128, &stream->fh);
|
||||
if (readsize != 128) goto fail;
|
||||
if (tag_is_id3v1(buf, 128)) {
|
||||
stream->fh.length -= 128;
|
||||
Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L);
|
||||
/* FIXME: handle possible double-ID3v1 tags? */
|
||||
}
|
||||
}
|
||||
|
||||
/* do we know whether ape or lyrics3 is the first?
|
||||
* well, we don't: we need to handle that later... */
|
||||
|
||||
/* check for the _old_ MusicMatch tag at end. */
|
||||
if (stream->fh.length >= 68) {
|
||||
FS_fseek(&stream->fh, -48, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 48, &stream->fh);
|
||||
if (readsize != 48) goto fail;
|
||||
if (is_musicmatch(buf, 48)) {
|
||||
len = get_musicmatch_len(stream);
|
||||
if (len < 0) goto fail;
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes MusicMatch tag\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
/* APE tag may be at the end: read the footer */
|
||||
if (stream->fh.length >= 32) {
|
||||
FS_fseek(&stream->fh, -32, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 32, &stream->fh);
|
||||
if (readsize != 32) goto fail;
|
||||
if (tag_is_apetag(buf, 32)) {
|
||||
len = get_ape_len(buf);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->fh.length >= 15) {
|
||||
FS_fseek(&stream->fh, -15, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 15, &stream->fh);
|
||||
if (readsize != 15) goto fail;
|
||||
len = is_lyrics3tag(buf, 15);
|
||||
if (len == 2) {
|
||||
len = get_lyrics3v2_len(buf, 6);
|
||||
if (len >= stream->fh.length) goto fail;
|
||||
if (len < 15) goto fail;
|
||||
FS_fseek(&stream->fh, -len, SEEK_END);
|
||||
readsize = FS_fread(buf, 1, 11, &stream->fh);
|
||||
if (readsize != 11) goto fail;
|
||||
if (!verify_lyrics3v2(buf, 11)) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
|
||||
}
|
||||
else if (len == 1) {
|
||||
len = get_lyrics3v1_len(stream);
|
||||
if (len < 0) goto fail;
|
||||
stream->fh.length -= len;
|
||||
Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
fail:
|
||||
FS_rewind(&stream->fh);
|
||||
return (stream->fh.length > 0)? rc : -1;
|
||||
}
|
||||
|
||||
#endif /* USE_CODEC_MP3 */
|
|
@ -284,6 +284,9 @@
|
|||
<Unit filename="..\..\Quake\snd_mp3.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="..\..\Quake\snd_mp3tag.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="..\..\Quake\snd_mp3.h" />
|
||||
<Unit filename="..\..\Quake\snd_opus.c">
|
||||
<Option compilerVar="CC" />
|
||||
|
|
|
@ -283,6 +283,9 @@
|
|||
<Unit filename="..\..\Quake\snd_mp3.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="..\..\Quake\snd_mp3tag.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="..\..\Quake\snd_mp3.h" />
|
||||
<Unit filename="..\..\Quake\snd_opus.c">
|
||||
<Option compilerVar="CC" />
|
||||
|
|
|
@ -561,6 +561,10 @@
|
|||
RelativePath="..\..\Quake\snd_mp3.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\Quake\snd_mp3tag.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\Quake\snd_opus.c"
|
||||
>
|
||||
|
|
|
@ -561,6 +561,10 @@
|
|||
RelativePath="..\..\Quake\snd_mp3.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\Quake\snd_mp3tag.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\Quake\snd_opus.c"
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue