-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAiffParser.java
More file actions
132 lines (109 loc) · 4.83 KB
/
AiffParser.java
File metadata and controls
132 lines (109 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package me.tamkungz.codecmedia.internal.audio.aiff;
import me.tamkungz.codecmedia.CodecMediaException;
import me.tamkungz.codecmedia.internal.audio.BitrateMode;
public final class AiffParser {
private AiffParser() {
}
public static AiffProbeInfo parse(byte[] bytes) throws CodecMediaException {
if (!isLikelyAiff(bytes)) {
throw new CodecMediaException("Not an AIFF file");
}
int offset = 12;
Integer channels = null;
Integer bitsPerSample = null;
Integer sampleRate = null;
Long frameCount = null;
while (offset + 8 <= bytes.length) {
String chunkId = readAscii(bytes, offset, 4);
int chunkSize = readBeInt(bytes, offset + 4);
if (chunkSize < 0) {
throw new CodecMediaException("Invalid AIFF chunk size: " + chunkSize);
}
int chunkDataStart = offset + 8;
if (chunkDataStart + chunkSize > bytes.length) {
throw new CodecMediaException("AIFF chunk exceeds file bounds: " + chunkId);
}
if ("COMM".equals(chunkId)) {
if (chunkSize < 18) {
throw new CodecMediaException("AIFF COMM chunk too small");
}
channels = readBeShort(bytes, chunkDataStart);
frameCount = readBeUInt32(bytes, chunkDataStart + 2);
bitsPerSample = readBeShort(bytes, chunkDataStart + 6);
sampleRate = decodeExtended80ToIntHz(bytes, chunkDataStart + 8);
}
int padded = (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
offset = chunkDataStart + padded;
}
if (channels == null || bitsPerSample == null || sampleRate == null || frameCount == null) {
throw new CodecMediaException("AIFF missing required COMM chunk fields");
}
if (channels <= 0 || bitsPerSample <= 0 || sampleRate <= 0 || frameCount < 0) {
throw new CodecMediaException("Invalid AIFF format values");
}
long durationMillis = (frameCount * 1000L) / sampleRate;
long byteRate = (long) sampleRate * channels * bitsPerSample / 8L;
int bitrateKbps = (int) ((byteRate * 8L) / 1000L);
return new AiffProbeInfo(durationMillis, bitrateKbps, sampleRate, channels, BitrateMode.CBR);
}
public static boolean isLikelyAiff(byte[] bytes) {
return bytes != null
&& bytes.length >= 12
&& bytes[0] == 'F'
&& bytes[1] == 'O'
&& bytes[2] == 'R'
&& bytes[3] == 'M'
&& bytes[8] == 'A'
&& bytes[9] == 'I'
&& bytes[10] == 'F'
&& (bytes[11] == 'F' || bytes[11] == 'C');
}
private static int decodeExtended80ToIntHz(byte[] bytes, int offset) throws CodecMediaException {
if (offset + 10 > bytes.length) {
throw new CodecMediaException("Unexpected end of AIFF data");
}
int exp = ((bytes[offset] & 0x7F) << 8) | (bytes[offset + 1] & 0xFF);
long mantissa = 0;
for (int i = 0; i < 8; i++) {
mantissa = (mantissa << 8) | (bytes[offset + 2 + i] & 0xFFL);
}
if (exp == 0 || mantissa == 0) {
return 0;
}
int shift = exp - 16383 - 63;
long value;
if (shift >= 0) {
value = mantissa << Math.min(shift, 30);
} else {
value = mantissa >>> Math.min(-shift, 63);
}
if (value <= 0 || value > Integer.MAX_VALUE) {
throw new CodecMediaException("Unsupported AIFF sample rate encoding");
}
return (int) value;
}
private static String readAscii(byte[] bytes, int offset, int len) throws CodecMediaException {
if (offset + len > bytes.length) {
throw new CodecMediaException("Unexpected end of AIFF data");
}
return new String(bytes, offset, len, java.nio.charset.StandardCharsets.US_ASCII);
}
private static int readBeShort(byte[] bytes, int offset) throws CodecMediaException {
if (offset + 2 > bytes.length) {
throw new CodecMediaException("Unexpected end of AIFF data");
}
return ((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF);
}
private static int readBeInt(byte[] bytes, int offset) throws CodecMediaException {
if (offset + 4 > bytes.length) {
throw new CodecMediaException("Unexpected end of AIFF data");
}
return ((bytes[offset] & 0xFF) << 24)
| ((bytes[offset + 1] & 0xFF) << 16)
| ((bytes[offset + 2] & 0xFF) << 8)
| (bytes[offset + 3] & 0xFF);
}
private static long readBeUInt32(byte[] bytes, int offset) throws CodecMediaException {
return readBeInt(bytes, offset) & 0xFFFFFFFFL;
}
}