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:
Ozkan Sezer 2019-12-12 11:37:32 +00:00
parent b2bf251a32
commit ffe43cf024
16 changed files with 391 additions and 208 deletions

View file

@ -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" />

View file

@ -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" />

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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
View 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 */

View file

@ -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" />

View file

@ -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" />

View file

@ -561,6 +561,10 @@
RelativePath="..\..\Quake\snd_mp3.c"
>
</File>
<File
RelativePath="..\..\Quake\snd_mp3tag.c"
>
</File>
<File
RelativePath="..\..\Quake\snd_opus.c"
>

View file

@ -561,6 +561,10 @@
RelativePath="..\..\Quake\snd_mp3.c"
>
</File>
<File
RelativePath="..\..\Quake\snd_mp3tag.c"
>
</File>
<File
RelativePath="..\..\Quake\snd_opus.c"
>