-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAiffParser.java
More file actions
151 lines (125 loc) · 5.69 KB
/
AiffParser.java
File metadata and controls
151 lines (125 loc) · 5.69 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package me.tamkungz.codecmedia.internal.audio.aiff;
import me.tamkungz.codecmedia.CodecMediaException;
import me.tamkungz.codecmedia.internal.audio.BitrateMode;
public final class AiffParser {
private static final String AIFC_COMPRESSION_NONE = "NONE";
private static final String AIFC_COMPRESSION_SOWT = "sowt";
private AiffParser() {
}
public static AiffProbeInfo parse(byte[] bytes) throws CodecMediaException {
if (!isLikelyAiff(bytes)) {
throw new CodecMediaException("Not an AIFF file");
}
boolean aifc = bytes[11] == 'C';
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);
if (aifc) {
if (chunkSize < 22) {
throw new CodecMediaException("AIFC COMM chunk missing compression type");
}
String compressionType = readAscii(bytes, chunkDataStart + 18, 4);
validateAifcCompressionType(compressionType);
}
}
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);
}
private static void validateAifcCompressionType(String compressionType) throws CodecMediaException {
if (AIFC_COMPRESSION_NONE.equals(compressionType) || AIFC_COMPRESSION_SOWT.equals(compressionType)) {
return;
}
throw new CodecMediaException("Unsupported AIFC compression type: " + compressionType);
}
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;
}
}