Created
March 16, 2023 10:35
-
-
Save countingpine/816e79f73ca48a31050fe3e87b4418da to your computer and use it in GitHub Desktop.
MPEG format dump utility by Ralph Giles - https://svn.xiph.org/experimental/giles/mp3dump.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MP3 header is 32 bits | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| sync word |V| L |E| rate |frq|P|R| mode |C|O|EM | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
sync word = 1111 1111 111x (x is zero for MPEG 2.5 VBR extension) | |
V (version) 1 = MPEG | |
L (layer) 01 = layer 3 | |
E (error protection flag) 1 = no crc | |
rate (bitrate enum) | |
frq (sampling frequency enum) | |
P (padded frame flag) 0 = no padding | |
R (reserved or unknown bit) | |
mode (joint stereo mode) | |
C (copyright flag) | |
O (original/copy flag) 1 = original | |
EM (emphasis) 00 = none | |
bitrate is a 4 bit enum, with a lookup table that varies by version | |
according to the following table | |
version: MPEG-1 Layer 1 Layer 2 Layer 3 MPEG-2 LSF Layer 1 Layer 2/3 | |
enum: | |
0000=0 0 0 0 0 0 | |
0001=1 32k 32k 32k 32k 8k | |
0010=2 64k 48k 40k 48k 16k | |
0011=3 96k 56k 48k 56k 24k | |
0100=4 128k 64k 56k 64k 32k | |
0101=5 160k 80k 64k 80k 40k | |
0110=6 192k 96k 80k 96k 48k | |
0111=7 224k 112k 96k 112k 56k | |
1000=8 256k 128k 112k 128k 64k | |
1001=9 288k 160k 128k 144k 80k | |
1010=10 320k 192k 160k 160k 96k | |
1011=11 352k 224k 196k 176k 112k | |
1100=12 384k 256k 224k 192k 128k | |
1101=13 416k 320k 256k 224k 144k | |
1110=14 448k 384k 320k 256k 160k | |
1111=15 n/a n/a n/a n/a n/a | |
In summary, for values v in the header from 1-14, the bitrate is: | |
MPEG-1 layer 1: 32*v | |
MPEG-1 layer 2: various | |
MPEG-1 layer 3: various |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* MPEG format dump utility */ | |
#include <stdio.h> | |
#include <stdlib.h> | |
typedef struct { | |
int version; | |
int layer; | |
int errp; | |
int bitrate; | |
int freq; | |
int pad; | |
int priv; | |
int mode; | |
int modex; | |
int copyright; | |
int original; | |
int emphasis; | |
} mp3header; | |
static int parse(const unsigned char *p, mp3header *header) | |
{ | |
const int bitrates[16] = | |
{0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, | |
112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}; | |
const int samplerates[4] = {44100, 48000, 32000}; | |
header->version = (p[1] & 0x08) >> 3; | |
header->layer = 4 - ((p[1] & 0x06) >> 1); | |
header->errp = (p[1] & 0x01); | |
header->bitrate = bitrates[(p[2] & 0xf0) >> 4]; | |
header->freq = samplerates[(p[2] & 0x0c) >> 2]; | |
header->pad = (p[2] & 0x02) >> 1; | |
header->priv = (p[2] & 0x01); | |
header->mode = (p[3] & 0xc0) >> 6; | |
header->modex = (p[3] & 0x30) >> 4; | |
header->copyright = (p[3] & 0x08) >> 3; | |
header->original = (p[3] & 0x04) >> 2; | |
header->emphasis = (p[3] & 0x03); | |
return 0; | |
} | |
/* calculate the size of an mp3 frame from its header */ | |
static int framesize(mp3header *header) | |
{ | |
int size; | |
int scale; | |
if (header->layer == 1) scale = 48; | |
else scale = 144; | |
size = header->bitrate * scale / header->freq; | |
/* divide by an extra factor of 2 for MPEG-2? */ | |
if (header->pad) size += 1; | |
return size; | |
} | |
static int dump_header(mp3header *header, FILE *out) | |
{ | |
fprintf(out, " version %d layer %d", header->version, header->layer); | |
if (header->version == 1 && header->layer == 1) | |
fprintf(out, " (MPEG-1 layer 2)"); | |
else if (header->version == 1 && header->layer == 2) | |
fprintf(out, " (MPEG-1 layer 2)"); | |
else if (header->version == 1 && header->layer == 3) | |
fprintf(out, " (MPEG-1 layer 3)"); | |
fprintf(out, " %d kbps %d Hz", header->bitrate/1000, header->freq); | |
fprintf(out, " %d byte frame", framesize(header)); | |
fprintf(out, "\n"); | |
return 0; | |
} | |
static int is_mpack(const unsigned char *p, const unsigned char *e) { | |
/* do we have enough room to see a 4 byte header? */ | |
if (p > e) return 0; | |
if (e - p < 4) return 0; | |
/* do we have a sync pattern? */ | |
if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01 && p[3] == 0xba) { | |
return 1; | |
} | |
return 0; | |
} | |
static int is_mpsys(const unsigned char *p, const unsigned char *e) { | |
/* do we have enough room to see a 4 byte header? */ | |
if (p > e) return 0; | |
if (e - p < 4) return 0; | |
/* do we have a sync pattern? */ | |
if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01 && p[3] == 0xbb) { | |
return 1; | |
} | |
return 0; | |
} | |
static int is_mpmap(const unsigned char *p, const unsigned char *e) { | |
/* do we have enough room to see a 4 byte header? */ | |
if (p > e) return 0; | |
if (e - p < 4) return 0; | |
/* do we have a sync pattern? */ | |
if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01 && p[3] == 0xbc) { | |
return 1; | |
} | |
return 0; | |
} | |
static int is_mp3(const unsigned char *p, const unsigned char *e) { | |
/* do we have enough room to see a 4 byte header? */ | |
if (p > e) return 0; | |
if (e - p < 4) return 0; | |
/* do we have a sync pattern? */ | |
if (p[0] == 0xff && (p[1]&0xe0) == 0xe0) { | |
/* do we have any illegal field values? */ | |
if (((p[1] & 0x06) >> 1) == 0) return 0; /* no layer 4 */ | |
if (((p[2] & 0xf0) >> 4) == 15) return 0; /* bitrate can't be 1111 */ | |
if (((p[2] & 0x0c) >> 2) == 3) return 0; /* samplerate can't be 11 */ | |
/* looks like a sync'd header */ | |
return 1; | |
} | |
return 0; | |
} | |
static int is_id3(const unsigned char *p, const unsigned char *e) { | |
/* do we have enough room to see a 10 byte header? */ | |
if (p > e) return 0; | |
if (e - p < 10) return 0; | |
/* do we have a sync pattern? */ | |
if (p[0] == 'I' && p[1] == 'D' && p[2] == '3') { | |
if (p[3] == 0xff || p[4] == 0xff) return 0; /* illegal version */ | |
if (p[6] & 0x80 || p[7] & 0x80 || | |
p[8] & 0x80) return 0; /* bad length encoding */ | |
/* looks like an id3 header */ | |
return 1; | |
} | |
return 0; | |
} | |
int dump(FILE *in, FILE *out) | |
{ | |
mp3header header; | |
unsigned char *buf, *p, *e, *q; | |
long length, skip; | |
long hbytes = 0; | |
fseek(in, 0, SEEK_END); | |
length = ftell(in); | |
if (length <= 0) { | |
fprintf(stderr, "couldn't find the end of the file\n"); | |
return 1; | |
} | |
fprintf(out, "File has %ld bytes\n", length); | |
fseek(in, 0, SEEK_SET); | |
buf = malloc(length); | |
if (!buf) { | |
fprintf(stderr, "couldn't allocate buffer for file data" | |
" (%ld bytes)\n", length); | |
return 2; | |
} | |
if (fread(buf, 1, length, in) < length) { | |
fprintf(stderr, "couldn't read all of the file\n"); | |
return 3; | |
} | |
e = buf + length - 2; | |
p = buf; | |
q = p; | |
while (p < e) { | |
if (is_id3(p, e)) { | |
if (p - q) fprintf(out, "LOST SYNC\n"); | |
skip = 10 + (p[9] | (p[8] << 7) | (p[7] << 14) | (p[6] << 21)); | |
fprintf(out, " id3 header at 0x%08lx (%ld bytes)\n", | |
(long)(p-buf), skip); | |
p += skip; | |
hbytes += skip; | |
q = p; | |
} else if (is_mp3(p, e)) { | |
if (p - q) fprintf(out, "LOST SYNC\n"); | |
parse(p, &header); | |
skip = framesize(&header); | |
if (skip <= 4) { | |
fprintf(out, " can't calculate frame size\n"); | |
break; | |
} | |
fprintf(out, " mp3 header at 0x%08lx (%ld bytes)\n", | |
(long)(p-buf), skip); | |
dump_header(&header, out); | |
p += skip; | |
hbytes += 4; | |
q = p; | |
} else { | |
if (is_mpack(p, e)) { | |
fprintf(out, "MPEG pack header\n"); | |
} else if (is_mpsys(p, e)) { | |
fprintf(out, "MPEG system header\n"); | |
} else if (is_mpmap(p, e)) { | |
fprintf(out, "MPEG program map\n"); | |
} | |
//fprintf(out, "%08lx %02x\n", (long)(p-buf), (int)p[0]); | |
p++; | |
} | |
} | |
free(buf); | |
fprintf(stdout, "Framing overhead %ld/%ld bytes (%02.3lf%%)\n", | |
hbytes, length, 100.0*hbytes/length); | |
return 0; | |
} | |
void usage(const char *name) | |
{ | |
fprintf(stdout, "usage: %s file.mp3 [file2.mp3 ...]\n", name); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
int i; | |
FILE *in, *out = stdout; | |
if (argc < 2) { | |
usage(argv[0]); | |
return 0; | |
} | |
for (i = 1; i < argc; i++) { | |
if (argv[i][0] == '-' && argv[i][1] == '\0') { | |
dump(stdin, out); | |
} else { | |
in = fopen(argv[i], "rb"); | |
if (!in) { | |
fprintf(stderr, "couldn't open '%s'\n", argv[i]); | |
continue; | |
} | |
dump(in, out); | |
fclose(in); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment