-
Notifications
You must be signed in to change notification settings - Fork 0
/
QOADecoder.java
155 lines (144 loc) · 3.92 KB
/
QOADecoder.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
// Generated automatically with "fut". Do not edit.
/**
* Decoder of the "Quite OK Audio" format.
*/
public abstract class QOADecoder extends QOABase
{
/**
* Reads a byte from the stream.
* Returns the unsigned byte value or -1 on EOF.
*/
protected abstract int readByte();
/**
* Seeks the stream to the given position.
* @param position File offset in bytes.
*/
protected abstract void seekToByte(int position);
private int buffer;
private int bufferBits;
private int readBits(int bits)
{
while (this.bufferBits < bits) {
int b = readByte();
if (b < 0)
return -1;
this.buffer = this.buffer << 8 | b;
this.bufferBits += 8;
}
this.bufferBits -= bits;
int result = this.buffer >> this.bufferBits;
this.buffer &= (1 << this.bufferBits) - 1;
return result;
}
private int totalSamples;
private int positionSamples;
/**
* Reads the file header.
* Returns <code>true</code> if the header is valid.
*/
public final boolean readHeader()
{
if (readByte() != 'q' || readByte() != 'o' || readByte() != 'a' || readByte() != 'f')
return false;
this.bufferBits = this.buffer = 0;
this.totalSamples = readBits(32);
if (this.totalSamples <= 0)
return false;
this.frameHeader = readBits(32);
if (this.frameHeader <= 0)
return false;
this.positionSamples = 0;
int channels = getChannels();
return channels > 0 && channels <= 8 && getSampleRate() > 0;
}
/**
* Returns the file length in samples per channel.
*/
public final int getTotalSamples()
{
return this.totalSamples;
}
private int getMaxFrameBytes()
{
return 8 + getChannels() * 2064;
}
private boolean readLMS(int[] result)
{
for (int i = 0; i < 4; i++) {
int hi = readByte();
if (hi < 0)
return false;
int lo = readByte();
if (lo < 0)
return false;
result[i] = ((hi ^ 128) - 128) << 8 | lo;
}
return true;
}
/**
* Reads and decodes a frame.
* Returns the number of samples per channel.
* @param samples PCM samples.
*/
public final int readFrame(short[] samples)
{
if (this.positionSamples > 0 && readBits(32) != this.frameHeader)
return -1;
int samplesCount = readBits(16);
if (samplesCount <= 0 || samplesCount > 5120 || samplesCount > this.totalSamples - this.positionSamples)
return -1;
int channels = getChannels();
int slices = (samplesCount + 19) / 20;
if (readBits(16) != 8 + channels * (16 + slices * 8))
return -1;
final LMS[] lmses = new LMS[8];
for (int _i0 = 0; _i0 < 8; _i0++) {
lmses[_i0] = new LMS();
}
for (int c = 0; c < channels; c++) {
if (!readLMS(lmses[c].history) || !readLMS(lmses[c].weights))
return -1;
}
for (int sampleIndex = 0; sampleIndex < samplesCount; sampleIndex += 20) {
for (int c = 0; c < channels; c++) {
int scaleFactor = readBits(4);
if (scaleFactor < 0)
return -1;
scaleFactor = SCALE_FACTORS[scaleFactor];
int sampleOffset = sampleIndex * channels + c;
for (int s = 0; s < 20; s++) {
int quantized = readBits(3);
if (quantized < 0)
return -1;
if (sampleIndex + s >= samplesCount)
continue;
int dequantized = dequantize(quantized, scaleFactor);
int reconstructed = clamp(lmses[c].predict() + dequantized, -32768, 32767);
lmses[c].update(reconstructed, dequantized);
samples[sampleOffset] = (short) reconstructed;
sampleOffset += channels;
}
}
}
this.positionSamples += samplesCount;
return samplesCount;
}
/**
* Seeks to the given time offset.
* Requires the input stream to be seekable with <code>SeekToByte</code>.
* @param position Position from the beginning of the file.
*/
public final void seekToSample(int position)
{
int frame = position / 5120;
seekToByte(frame == 0 ? 12 : 8 + frame * getMaxFrameBytes());
this.positionSamples = frame * 5120;
}
/**
* Returns <code>true</code> if all frames have been read.
*/
public final boolean isEnd()
{
return this.positionSamples >= this.totalSamples;
}
}