/*- * Copyright (c) 2001 Yar Tikhiy * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #ifndef lint static const char rcsid[] = "$Id: mp3ck.c,v 1.4 2005/04/03 04:49:22 yarq Exp $"; #endif #define MPEG_HDR_SIZE 4 #define MPEG_SPF 1152L /* samples per frame XXX: not true for L1 */ #define MPEG_VERID(c) (((c) >> 3) & 3) #define MPEG_LAYERD(c) (((c) >> 1) & 3) #define MPEG_PROT(c) ((c) & 1) #define MPEG_BRIDX(c) (((c) >> 4) & 0xf) #define MPEG_FRIDX(c) (((c) >> 2) & 3) #define MPEG_PAD(c) (((c) >> 1) & 1) #define MPEG_PRIV(c) ((c) & 1) #define MPEG_CHAN(c) (((c) >> 6) & 3) #define MPEG_MODEX(c) (((c) >> 4) & 3) #define MPEG_CPRT(c) (((c) >> 3) & 1) #define MPEG_ORIG(c) (((c) >> 2) & 1) #define MPEG_EMPH(c) ((c) & 3) #define MPEG_LAYER(c) (4 - MPEG_LAYERD(c)) /* * NB: ver == 4 for MPEG2.5 */ #define MPEG_VER(c) (4 - MPEG_VERID(c)) #define MPEG_BRSELECTOR(ver, layer) \ ((ver) == 1 ? (layer) - 1 : ((layer) == 1 ? 3 : 4)) #define MPEG_BITRATE(c, ver, layer) \ (bitratetab[MPEG_BRSELECTOR((ver), (layer))][MPEG_BRIDX(c)]) #define MPEG_FREQ(c, ver) \ (freqtab[(ver) == 4 ? 2 : (ver) - 1][MPEG_FRIDX(c)]) #define ID3V1_SIZE 128 #define ID3V1_LABEL_LEN 3 #define ID3V2_LABEL_LEN 3 /* * This struct doesn't include the "ID3" label on purpose. * See the function id3v2(). */ struct id3v2 { /* unsigned char id3[3]; */ unsigned char major; unsigned char revision; unsigned char flags; unsigned char size[4]; }; #define ID3V2_FLAG_UNSYNC 0x80 #define ID3V2_FLAG_EXTHDR 0x40 #define ID3V2_FLAG_EXP 0x20 #define ID3V2_FLAG_FOOTER 0x10 #define SYNCSAFE32(n) ((n)[3] + 128 * ((n)[2] + 128 * ((n)[1] + 128 * (n)[0]))) #define MSG_NONE 0 /* don't forget to update this! */ #define MSG_ERR 0 #define MSG_WRN 1 #define MSG_NTC 2 #define MSG_NFO 3 #define MSG_DBG 4 #define MSG_ALL 4 /* don't forget to update this! */ int pedantic = 0; /* treat some warnings as errors */ int time_offset = 0; /* show offsets as mm:ss if possible */ int verbosity = MSG_NTC; /* how much output to produce */ static int check(const char *); static void frametotime(long, long *, long *); static void usage(void); int main(int argc, char **argv) { int opt; int rc, maxrc; while ((opt = getopt(argc, argv, "dhpqstv")) != -1) switch (opt) { case 'd': verbosity = MSG_ALL; break; case 'p': pedantic = 1; break; case 'q': verbosity--; break; case 's': verbosity = MSG_NONE; break; case 't': time_offset = 1; break; case 'v': verbosity++; break; case 'h': case '?': usage(); /* NOTREACHED */ } argc -= optind; argv += optind; if (argc < 1) usage(); maxrc = 0; for (; *argv; argv++) { rc = check(*argv); if (rc > maxrc) maxrc = rc; } /* * 0 - OK * 1 - MPEG format error * 2 - file or system error */ if (maxrc > 2) maxrc = 2; exit(maxrc); return (0); /* make cc happy */ } long bitratetab[][16] = { /* 0: V1, L1 */ { -1, 32000L, 64000L, 96000L, 128000L, 160000L, 192000L, 224000L, 256000L, 288000L, 320000L, 352000L, 384000L, 416000L, 448000L, -1 }, /* 1: V1, L2 */ { -1, 32000L, 48000L, 56000L, 64000L, 80000L, 96000L, 112000L, 128000L, 160000L, 192000L, 224000L, 256000L, 320000L, 384000L, -1 }, /* 2: V1, L3 */ { -1, 32000L, 40000L, 48000L, 56000L, 64000L, 80000L, 96000L, 112000L, 128000L, 160000L, 192000L, 224000L, 256000L, 320000L, -1 }, /* 3: V2, L1 */ { -1, 32000L, 48000L, 56000L, 64000L, 80000L, 96000L, 112000L, 128000L, 144000L, 160000L, 176000L, 192000L, 224000L, 256000L, -1 }, /* 4: V2, L2 & L3 */ { -1, 8000L, 16000L, 24000L, 32000L, 40000L, 48000L, 56000L, 64000L, 80000L, 96000L, 112000L, 128000L, 144000L, 160000L, -1 } }; long freqtab[][4] = { { 44100L, 48000L, 32000L, -1 }, { 22050L, 24000L, 16000L, -1 }, { 11025L, 12000L, 8000L, -1 } }; enum state {START, SYNC, FRAME, ID3V1, ID3V2, FINISH, QUIT, NOSTATE}; const char *file; /* current file name */ FILE *fp; /* current file handle */ int c; /* current byte */ int insync; /* if we're seeing what we expected */ long goodend; /* offset right beyond last good frame or tag */ long nframes; /* total audio frames seen */ long where; /* starting offset of current frame/tag (for wrn()) */ int rc; /* what to return to upper layer */ int vbr; /* current file has VBR */ /* for final info/statistics */ long kbps; /* sum of kbps of seen frames if VBR */ long hz; int chanmode; int hascrc; int hasid3v1; int hasid3v2; static enum state start(void); static enum state sync1(void); static enum state frame(void); static enum state id3v1(void); static enum state id3v2(void); static enum state finish(void); static void wrn(int, long, const char *, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) #endif ; static void wrnsync(void); static void diffhdr(const char *, const char *); static int check(const char *fn) { enum state state; file = fn; for (state = START; state != QUIT;) switch (state) { case START: state = start(); break; case SYNC: state = sync1(); break; case FRAME: state = frame(); break; case ID3V1: state = id3v1(); break; case ID3V2: state = id3v2(); break; case FINISH: state = finish(); break; default: printf("Can't happen: unknown state\n"); exit(2); } return (rc); } static enum state start() { if ((fp = fopen(file, "rb")) == NULL) { rc = 2; perror(file); return (QUIT); } c = EOF; insync = 1; goodend = 0; nframes = 0; hasid3v1 = hasid3v2 = 0; vbr = 0; rc = 0; return (SYNC); } /* * When transitioning to this state: * Set `c' to EOF to proceed to the next byte. * Leave `c' untouched to restart sync'ing from the current position. */ static enum state sync1() { if (c == EOF) /* allow restarting from the same stream position */ c = getc(fp); where = ftell(fp) - 1; switch (c) { case EOF: if (ftell(fp) == 0) { rc = 1; wrn(MSG_WRN, -1, "empty file"); } else if (nframes == 0) { rc = 1; wrn(MSG_WRN, -1, "no audio frames"); } else if (!insync) { if (pedantic) rc = 1; wrn(MSG_NTC, goodend, "trailing junk"); } return (FINISH); /* NOTREACHED */ case 'I': if (insync) return (ID3V2); else { c = EOF; return (SYNC); } /* NOTREACHED */ case 'T': if (insync) return (ID3V1); else { c = EOF; return (SYNC); } /* NOTREACHED */ case 0xff: return (FRAME); /* NOTREACHED */ default: wrnsync(); insync = 0; c = EOF; return (SYNC); /* NOTREACHED */ } return (NOSTATE); } static enum state frame() { int i; int ver, layer, prot, pad; long bitrate, freq; long framelen, n; char hdr[MPEG_HDR_SIZE - 1]; static char phdr[MPEG_HDR_SIZE - 1]; i = 0; if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "unexpected EOF at frame header+1"); return (FINISH); } if ((c & 0xe0) != 0xe0) { wrnsync(); insync = 0; return (SYNC); } hdr[i++] = c; ver = MPEG_VER(c); switch (ver) { case 1: case 2: case 4: /* 2.5 */ break; case 3: rc = 1; wrn(MSG_WRN, 0, "invalid MPEG version ID"); insync = 0; return (SYNC); /* NOTREACHED */ default: abort(); /* NOTREACHED */ } layer = MPEG_LAYER(c); switch (layer) { case 1: rc = 1; wrn(MSG_WRN, 0, "layer I unsupported"); insync = 0; return (SYNC); /* NOTREACHED */ case 2: case 3: break; case 4: rc = 1; wrn(MSG_WRN, 0, "invalid layer description"); insync = 0; return (SYNC); /* NOTREACHED */ default: abort(); /* NOTREACHED */ } prot = MPEG_PROT(c); #if notyet crc = 0xffff; #endif if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "unexpected EOF at frame header+2"); return (FINISH); } #if notyet if (prot) crcupdate(c, &crc); #endif hdr[i++] = c; bitrate = MPEG_BITRATE(c, ver, layer); if (bitrate < 0) { rc = 1; wrn(MSG_WRN, 0, "invalid bitrate index"); insync = 0; return (SYNC); } freq = MPEG_FREQ(c, ver); if (freq < 0) { rc = 1; wrn(MSG_WRN, 0, "invalid sampling frequency index"); insync = 0; return (SYNC); } pad = MPEG_PAD(c); if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "unexpected EOF at frame header+3"); return (FINISH); } #if notyet if (prot) crcupdate(c, &crc); #endif hdr[i++] = c; framelen = MPEG_SPF / 8 * bitrate / freq + pad; if (!insync) { rc = 1; wrn(MSG_WRN, 0, "sync after %ld bytes of junk,", where - goodend); } wrn(MSG_DBG, -1, "frame br %ld hz %ld pad %d%s len %ld " "at %#lx next %#lx", bitrate, freq, pad, prot ? " CRC" : "", framelen, ftell(fp) - MPEG_HDR_SIZE, ftell(fp) - MPEG_HDR_SIZE + framelen); if (nframes) { diffhdr(phdr, hdr); if (vbr) kbps += bitrate / 1000; } else { kbps = bitrate / 1000; hz = freq; hascrc = prot; chanmode = MPEG_CHAN(c); } memcpy(phdr, hdr, sizeof(hdr) / sizeof(hdr[0])); /* skip the rest of the frame */ for (n = framelen - MPEG_HDR_SIZE; n; n--) if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "last frame incomplete " "(%ld of %ld bytes missing)", n, framelen); return (FINISH); } c = EOF; insync = 1; goodend = ftell(fp); nframes++; return (SYNC); } static enum state id3v1() { int n; if ((c = getc(fp)) == EOF) { if (pedantic) rc = 1; wrn(MSG_NTC, goodend, "trailing junk (truncated ID3v1 tag?)"); return (FINISH); } if (c != 'A') { insync = 0; return (SYNC); } if ((c = getc(fp)) == EOF) { if (pedantic) rc = 1; wrn(MSG_NTC, goodend, "trailing junk (truncated ID3v1 tag?)"); return (FINISH); } if (c != 'G') { insync = 0; return (SYNC); } hasid3v1 = 1; wrn(MSG_DBG, -1, "ID3v1 tag at %#lx", ftell(fp) - 3); for (n = ID3V1_SIZE - ID3V1_LABEL_LEN; n; n--) if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "ID3v1 tag incomplete " "(%d of %d bytes missing)", n, ID3V1_SIZE); return (FINISH); } if ((c = getc(fp)) == EOF) return (FINISH); rc = 1; wrn(MSG_WRN, 0, "ID3v1 in the middle of the file"); insync = 1; return (SYNC); } static enum state id3v2() { long idlen, n; struct id3v2 id; if ((c = getc(fp)) == EOF) { if (pedantic) rc = 1; wrn(MSG_NTC, goodend, "trailing junk (truncated ID3v2 tag?)"); return (FINISH); } if (c != 'D') { wrnsync(); insync = 0; return (SYNC); } if ((c = getc(fp)) == EOF) { if (pedantic) rc = 1; wrn(MSG_NTC, goodend, "trailing junk (truncated ID3v2 tag?)"); return (FINISH); } if (c != '3') { wrnsync(); insync = 0; return (SYNC); } hasid3v2 = 1; if (fread(&id, sizeof(id), 1, fp) != 1) { rc = 1; wrn(MSG_WRN, 0, "ID3v2 tag incomplete"); return (FINISH); } if (id.major < 2 || id.major > 4) { rc = 1; wrn(MSG_WRN, 0, "ID3v2.%d.%d unsupported", id.major, id.revision); insync = 0; return (SYNC); } idlen = ID3V2_LABEL_LEN + sizeof(id) + SYNCSAFE32(id.size); if (id.flags & ID3V2_FLAG_FOOTER) idlen += ID3V2_LABEL_LEN + sizeof(id); wrn(MSG_DBG, -1, "ID3v2.%d.%d tag len %ld at %#lx next %#lx", id.major, id.revision, idlen, ftell(fp) - ID3V2_LABEL_LEN - sizeof(id), ftell(fp) - ID3V2_LABEL_LEN - sizeof(id) + idlen); for (n = idlen - ID3V2_LABEL_LEN - sizeof(id); n; n--) if ((c = getc(fp)) == EOF) { rc = 1; wrn(MSG_WRN, 0, "ID3v2 tag incomplete " "(%ld of %ld bytes missing)", n, idlen); return (FINISH); } c = EOF; insync = 1; goodend = ftell(fp); return (SYNC); } static enum state finish() { long mm, ss; const char *st; if (ferror(fp)) { rc = 2; perror(file); } fclose(fp); if (nframes) { frametotime(nframes, &mm, &ss); switch (chanmode) { case 0: st = "stereo"; break; case 1: st = "joint stereo"; break; case 2: st = "dual stereo"; break; case 3: st = "mono"; break; default: abort(); } wrn(MSG_NFO, -1, "%s%ld kbps %ld hz %s%s%s%s (%ld:%02ld)", vbr ? "VBR/" : "", vbr ? kbps / nframes : kbps, hz, st, hascrc ? " CRC" : "", hasid3v1 ? " ID3v1" : "", hasid3v2 ? " ID3v2" : "", mm, ss); } return (QUIT); } static void wrn(int level, long offset, const char *fmt, ...) { long mm, ss; va_list ap; if (level > verbosity) return; va_start(ap, fmt); printf("%s: ", file); vprintf(fmt, ap); if (offset >= 0) { if (time_offset && offset == 0) { frametotime(nframes, &mm, &ss); printf(" at %ld:%02ld", mm, ss); } else printf(" at byte offset %#lx", offset ? offset : where); } printf("\n"); va_end(ap); } /* * Don't print the same "lost sync" twice. */ static void wrnsync() { if (insync) { if (goodend == 0) { rc = 1; wrn(MSG_WRN, -1, "leading junk"); } else /* * warning on lost sync is issued after * its nature gets clear: leading or trailing junk, * broken middle of file etc. */ wrn(MSG_DBG, 0, "lost sync"); } } /* * Check MPEG header fields that must be consistent * through a whole file. */ static void diffhdr(const char *h1, const char *h2) { #define DIFFTEST(byte, macro, msg) \ if (MPEG_##macro(h1[byte]) != MPEG_##macro(h2[byte])) { \ rc = 1; \ wrn(MSG_WRN, 0, "MPEG " msg " inconsistency (%d -> %d)",\ MPEG_##macro(h1[byte]), MPEG_##macro(h2[byte])); \ } #define DIFFTEST2(byte, macro, msg) \ if (MPEG_##macro(h1[byte], MPEG_VER(h1[0])) != \ MPEG_##macro(h2[byte], MPEG_VER(h2[0]))) { \ rc = 1; \ wrn(MSG_WRN, 0, "MPEG " msg " inconsistency (%ld -> %ld)",\ MPEG_##macro(h1[byte], MPEG_VER(h1[0])), \ MPEG_##macro(h2[byte], MPEG_VER(h2[0]))); \ } DIFFTEST(0, VERID, "version ID"); DIFFTEST(0, LAYERD, "layer descriptor"); DIFFTEST(0, PROT, "protection bit"); /*DIFFTEST2(1, BITRATE, "bitrate");*/ if (!vbr && MPEG_BITRATE(h1[1], MPEG_VER(h1[0]), MPEG_LAYER(h1[0])) != MPEG_BITRATE(h2[1], MPEG_VER(h2[0]), MPEG_LAYER(h2[0]))) { vbr = 1; kbps *= nframes; /* if VBR, kbps holds sum of bitrates */ } DIFFTEST2(1, FREQ, "sampling rate frequency"); DIFFTEST(1, PRIV, "private bit"); DIFFTEST(2, CHAN, "channel mode"); DIFFTEST(2, CPRT, "copyright bit"); DIFFTEST(2, ORIG, "original bit"); DIFFTEST(2, EMPH, "emphasis"); } static void frametotime(long frame, long *min, long *sec) { long t; if (frame) t = frame * MPEG_SPF / hz; /* XXX uses global var */ else t = 0; /* Don't use hz in this case since it is 0 */ *min = t / 60; *sec = t % 60; } static void usage() { printf("usage: mp3ck [-dhqstv] file ...\n"); exit(2); }