/
CheapMP3.java
258 lines (225 loc) · 8.37 KB
/
CheapMP3.java
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.semantive.waveformandroid.waveform.soundfile;
import java.io.File;
import java.io.FileInputStream;
/**
* CheapMP3 represents an MP3 file by doing a "cheap" scan of the file,
* parsing the frame headers only and getting an extremely rough estimate
* of the volume level of each frame.
*
* Modified by Anna Stępień <anna.stepien@semantive.com>
*
*/
public class CheapMP3 extends CheapSoundFile {
public static Factory getFactory() {
return new Factory() {
public CheapSoundFile create() {
return new CheapMP3();
}
public String[] getSupportedExtensions() {
return new String[] { "mp3" };
}
};
}
// Member variables representing frame data
private int mNumFrames;
private int[] mFrameGains;
private int mFileSize;
private int mAvgBitRate;
private int mGlobalSampleRate;
private int mGlobalChannels;
// Member variables used during initialization
private int mMaxFrames;
private int mBitrateSum;
private int mMinGain;
private int mMaxGain;
public CheapMP3() {
}
public int getNumFrames() {
return mNumFrames;
}
public int getSamplesPerFrame() {
return 1152;
}
public int[] getFrameGains() {
return mFrameGains;
}
public int getFileSizeBytes() {
return mFileSize;
}
public int getAvgBitrateKbps() {
return mAvgBitRate;
}
public int getSampleRate() {
return mGlobalSampleRate;
}
public int getChannels() {
return mGlobalChannels;
}
public String getFiletype() {
return "MP3";
}
public void ReadFile(File inputFile)
throws java.io.FileNotFoundException,
java.io.IOException {
super.ReadFile(inputFile);
mNumFrames = 0;
mMaxFrames = 64; // This will grow as needed
mFrameGains = new int[mMaxFrames];
mBitrateSum = 0;
mMinGain = 255;
mMaxGain = 0;
// No need to handle filesizes larger than can fit in a 32-bit int
mFileSize = (int)mInputFile.length();
FileInputStream stream = new FileInputStream(mInputFile);
int pos = 0;
int offset = 0;
byte[] buffer = new byte[12];
while (pos < mFileSize - 12) {
// Read 12 bytes at a time and look for a sync code (0xFF)
while (offset < 12) {
offset += stream.read(buffer, offset, 12 - offset);
}
int bufferOffset = 0;
while (bufferOffset < 12 &&
buffer[bufferOffset] != -1)
bufferOffset++;
if (mProgressListener != null) {
boolean keepGoing = mProgressListener.reportProgress(
pos * 1.0 / mFileSize);
if (!keepGoing) {
break;
}
}
if (bufferOffset > 0) {
// We didn't find a sync code (0xFF) at position 0;
// shift the buffer over and try again
for (int i = 0; i < 12 - bufferOffset; i++)
buffer[i] = buffer[bufferOffset + i];
pos += bufferOffset;
offset = 12 - bufferOffset;
continue;
}
// Check for MPEG 1 Layer III or MPEG 2 Layer III codes
int mpgVersion = 0;
if (buffer[1] == -6 || buffer[1] == -5) {
mpgVersion = 1;
} else if (buffer[1] == -14 || buffer[1] == -13) {
mpgVersion = 2;
} else {
bufferOffset = 1;
for (int i = 0; i < 12 - bufferOffset; i++)
buffer[i] = buffer[bufferOffset + i];
pos += bufferOffset;
offset = 12 - bufferOffset;
continue;
}
// The third byte has the bitrate and samplerate
int bitRate;
int sampleRate;
if (mpgVersion == 1) {
// MPEG 1 Layer III
bitRate = BITRATES_MPEG1_L3[(buffer[2] & 0xF0) >> 4];
sampleRate = SAMPLERATES_MPEG1_L3[(buffer[2] & 0x0C) >> 2];
} else {
// MPEG 2 Layer III
bitRate = BITRATES_MPEG2_L3[(buffer[2] & 0xF0) >> 4];
sampleRate = SAMPLERATES_MPEG2_L3[(buffer[2] & 0x0C) >> 2];
}
if (bitRate == 0 || sampleRate == 0) {
bufferOffset = 2;
for (int i = 0; i < 12 - bufferOffset; i++)
buffer[i] = buffer[bufferOffset + i];
pos += bufferOffset;
offset = 12 - bufferOffset;
continue;
}
// From here on we assume the frame is good
mGlobalSampleRate = sampleRate;
int padding = (buffer[2] & 2) >> 1;
int frameLen = 144 * bitRate * 1000 / sampleRate + padding;
int gain;
if ((buffer[3] & 0xC0) == 0xC0) {
// 1 channel
mGlobalChannels = 1;
if (mpgVersion == 1) {
gain = ((buffer[10] & 0x01) << 7) +
((buffer[11] & 0xFE) >> 1);
} else {
gain = ((buffer[9] & 0x03) << 6) +
((buffer[10] & 0xFC) >> 2);
}
} else {
// 2 channels
mGlobalChannels = 2;
if (mpgVersion == 1) {
gain = ((buffer[9] & 0x7F) << 1) +
((buffer[10] & 0x80) >> 7);
} else {
gain = 0; // ???
}
}
mBitrateSum += bitRate;
mFrameGains[mNumFrames] = gain;
if (gain < mMinGain)
mMinGain = gain;
if (gain > mMaxGain)
mMaxGain = gain;
mNumFrames++;
if (mNumFrames == mMaxFrames) {
// We need to grow our arrays. Rather than naively
// doubling the array each time, we estimate the exact
// number of frames we need and add 10% padding. In
// practice this seems to work quite well, only one
// resize is ever needed, however to avoid pathological
// cases we make sure to always double the size at a minimum.
mAvgBitRate = mBitrateSum / mNumFrames;
int totalFramesGuess =
((mFileSize / mAvgBitRate) * sampleRate) / 144000;
int newMaxFrames = totalFramesGuess * 11 / 10;
if (newMaxFrames < mMaxFrames * 2)
newMaxFrames = mMaxFrames * 2;
int[] newOffsets = new int[newMaxFrames];
int[] newLens = new int[newMaxFrames];
int[] newGains = new int[newMaxFrames];
for (int i = 0; i < mNumFrames; i++) {
newGains[i] = mFrameGains[i];
}
mFrameGains = newGains;
mMaxFrames = newMaxFrames;
}
stream.skip(frameLen - 12);
pos += frameLen;
offset = 0;
}
// We're done reading the file, do some postprocessing
if (mNumFrames > 0)
mAvgBitRate = mBitrateSum / mNumFrames;
else
mAvgBitRate = 0;
}
static private int BITRATES_MPEG1_L3[] = {
0, 32, 40, 48, 56, 64, 80, 96,
112, 128, 160, 192, 224, 256, 320, 0 };
static private int BITRATES_MPEG2_L3[] = {
0, 8, 16, 24, 32, 40, 48, 56,
64, 80, 96, 112, 128, 144, 160, 0 };
static private int SAMPLERATES_MPEG1_L3[] = {
44100, 48000, 32000, 0 };
static private int SAMPLERATES_MPEG2_L3[] = {
22050, 24000, 16000, 0 };
};