Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lDEVinux
GitHub Repository: lDEVinux/eaglercraft
Path: blob/main/src/lwjgl/java/de/cuina/fireandfuel/CodecJLayerMP3.java
8649 views
1
package de.cuina.fireandfuel;
2
3
/*
4
* CodecJLayerMP3 - an ICodec interface for Paulscode Sound System
5
* Copyright (C) 2012 by fireandfuel from Cuina Team (http://www.cuina.byethost12.com/)
6
*
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public License
9
* as published by the Free Software Foundation; either version 3
10
* of the License, or (at your option) any later version.
11
*
12
* This program is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
13
* KIND, either express or implied. See the GNU Lesser General Public License for more details.
14
*
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this program; if not, see http://www.gnu.org/licenses/lgpl.txt
17
*/
18
19
import java.io.BufferedInputStream;
20
import java.io.IOException;
21
import java.net.URL;
22
23
import javax.sound.sampled.AudioFormat;
24
import javax.sound.sampled.AudioInputStream;
25
26
import javazoom.jl.decoder.Bitstream;
27
import javazoom.jl.decoder.Decoder;
28
import javazoom.jl.decoder.Header;
29
import javazoom.jl.decoder.Obuffer;
30
import javazoom.mp3spi.DecodedMpegAudioInputStream;
31
32
import paulscode.sound.ICodec;
33
import paulscode.sound.SoundBuffer;
34
import paulscode.sound.SoundSystemConfig;
35
import paulscode.sound.SoundSystemLogger;
36
37
/**
38
* The CodecJLayer class provides an ICodec interface to the external JLayer
39
* library.
40
*
41
* <b><br>
42
* <br>
43
* This software is based on or using the JLayer and mp3spi library from
44
* http://www.javazoom.net/javalayer/javalayer.html and Tritonus library from
45
* http://www.tritonus.org/.
46
*
47
* JLayer, mp3spi and Tritonus library are released under the conditions of
48
* GNU Library General Public License version 2 or (at your option)
49
* any later version of the License.
50
* </b><br>
51
*/
52
53
public class CodecJLayerMP3 implements ICodec
54
{
55
/**
56
* Used to return a current value from one of the synchronized
57
* boolean-interface methods.
58
*/
59
private static final boolean GET = false;
60
61
/**
62
* Used to set the value in one of the synchronized boolean-interface
63
* methods.
64
*/
65
private static final boolean SET = true;
66
67
/**
68
* Used when a parameter for one of the synchronized boolean-interface
69
* methods is not applicable.
70
*/
71
private static final boolean XXX = false;
72
73
/**
74
* True if there is no more data to read in.
75
*/
76
private boolean endOfStream = false;
77
78
/**
79
* True if the stream has finished initializing.
80
*/
81
private boolean initialized = false;
82
83
private Decoder decoder;
84
private Bitstream bitstream;
85
private DMAISObuffer buffer;
86
87
private Header mainHeader;
88
89
/**
90
* Audio format to use when playing back the wave data.
91
*/
92
private AudioFormat myAudioFormat = null;
93
94
/**
95
* Input stream to use for reading in pcm data.
96
*/
97
private DecodedMpegAudioInputStream myAudioInputStream = null;
98
99
/**
100
* Processes status messages, warnings, and error messages.
101
*/
102
private SoundSystemLogger logger;
103
104
public CodecJLayerMP3()
105
{
106
logger = SoundSystemConfig.getLogger();
107
}
108
109
@Override
110
public void reverseByteOrder(boolean b)
111
{
112
}
113
114
@Override
115
public boolean initialize(URL url)
116
{
117
initialized(SET, false);
118
cleanup();
119
if(url == null)
120
{
121
errorMessage("url null in method 'initialize'");
122
cleanup();
123
return false;
124
}
125
126
try
127
{
128
bitstream = new Bitstream(new BufferedInputStream(url.openStream()));
129
decoder = new Decoder();
130
131
mainHeader = bitstream.readFrame();
132
133
buffer = new DMAISObuffer(2);
134
decoder.setOutputBuffer(buffer);
135
136
int channels;
137
if(mainHeader.mode() < 3)
138
channels = 2;
139
else channels = 1;
140
141
bitstream.closeFrame();
142
bitstream.close();
143
144
myAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
145
mainHeader.frequency(), 16, channels, channels * 2, mainHeader.frequency(),
146
false);
147
148
AudioFormat mpegAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, -1.0f,
149
16, channels, channels * 2, -1.0f, false);
150
151
myAudioInputStream = new DecodedMpegAudioInputStream(myAudioFormat,
152
new AudioInputStream(new BufferedInputStream(url.openStream()),
153
mpegAudioFormat, -1));
154
myAudioInputStream.skip((int)(myAudioInputStream.getFormat().getFrameRate() * 0.018f) * myAudioInputStream.getFormat().getFrameSize());
155
} catch (Exception e)
156
{
157
errorMessage("Unable to set up input streams in method " + "'initialize'");
158
printStackTrace(e);
159
cleanup();
160
return false;
161
}
162
163
if(myAudioInputStream == null)
164
{
165
errorMessage("Unable to set up audio input stream in method " + "'initialize'");
166
cleanup();
167
return false;
168
}
169
170
endOfStream(SET, false);
171
initialized(SET, true);
172
return true;
173
}
174
175
@Override
176
public boolean initialized()
177
{
178
return initialized(GET, XXX);
179
}
180
181
@Override
182
public SoundBuffer read()
183
{
184
if(myAudioInputStream == null)
185
{
186
endOfStream(SET, true);
187
return null;
188
}
189
190
// Get the format for the audio data:
191
AudioFormat audioFormat = myAudioInputStream.getFormat();
192
193
// Check to make sure there is an audio format:
194
if(audioFormat == null)
195
{
196
errorMessage("Audio Format null in method 'read'");
197
endOfStream(SET, true);
198
return null;
199
}
200
201
// Variables used when reading from the audio input stream:
202
int bytesRead = 0, cnt = 0;
203
204
// Allocate memory for the audio data:
205
byte[] streamBuffer = new byte[SoundSystemConfig.getStreamingBufferSize()];
206
207
try
208
{
209
// Read until buffer is full or end of stream is reached:
210
while((!endOfStream(GET, XXX)) && (bytesRead < streamBuffer.length))
211
{
212
myAudioInputStream.execute();
213
if((cnt = myAudioInputStream.read(streamBuffer, bytesRead, streamBuffer.length
214
- bytesRead)) < 0)
215
{
216
endOfStream(SET, true);
217
break;
218
}
219
// keep track of how many bytes were read:
220
bytesRead += cnt;
221
}
222
} catch (IOException ioe)
223
{
224
225
/*
226
* errorMessage( "Exception thrown while reading from the " +
227
* "AudioInputStream (location #3)." ); printStackTrace( e ); return
228
* null;
229
*/// TODO: Figure out why this exceptions is being thrown at end of
230
// MP3 files!
231
endOfStream(SET, true);
232
return null;
233
} catch (ArrayIndexOutOfBoundsException e)
234
{
235
//this exception is thrown at the end of the mp3's
236
endOfStream(SET, true);
237
return null;
238
}
239
240
// Return null if no data was read:
241
if(bytesRead <= 0)
242
{
243
endOfStream(SET, true);
244
return null;
245
}
246
247
// Insert the converted data into a ByteBuffer:
248
// byte[] data = convertAudioBytes(streamBuffer,
249
// audioFormat.getSampleSizeInBits() == 16);
250
251
// Wrap the data into a SoundBuffer:
252
SoundBuffer buffer = new SoundBuffer(streamBuffer, audioFormat);
253
254
// Return the result:
255
return buffer;
256
}
257
258
@Override
259
public SoundBuffer readAll()
260
{
261
// Check to make sure there is an audio format:
262
if(myAudioFormat == null)
263
{
264
errorMessage("Audio Format null in method 'readAll'");
265
return null;
266
}
267
268
// Array to contain the audio data:
269
byte[] fullBuffer = null;
270
271
// Determine how much data will be read in:
272
int fileSize = myAudioFormat.getChannels() * (int) myAudioInputStream.getFrameLength()
273
* myAudioFormat.getSampleSizeInBits() / 8;
274
if(fileSize > 0)
275
{
276
// Allocate memory for the audio data:
277
fullBuffer = new byte[myAudioFormat.getChannels()
278
* (int) myAudioInputStream.getFrameLength()
279
* myAudioFormat.getSampleSizeInBits() / 8];
280
int read = 0, total = 0;
281
try
282
{
283
// Read until the end of the stream is reached:
284
while((read = myAudioInputStream.read(fullBuffer, total, fullBuffer.length - total)) != -1
285
&& total < fullBuffer.length)
286
{
287
total += read;
288
}
289
} catch (IOException e)
290
{
291
errorMessage("Exception thrown while reading from the "
292
+ "AudioInputStream (location #1).");
293
printStackTrace(e);
294
return null;
295
}
296
} else
297
{
298
// Total file size unknown.
299
300
// Variables used when reading from the audio input stream:
301
int totalBytes = 0, bytesRead = 0, cnt = 0;
302
byte[] smallBuffer = null;
303
304
// Allocate memory for a chunk of data:
305
smallBuffer = new byte[SoundSystemConfig.getFileChunkSize()];
306
307
// Read until end of file or maximum file size is reached:
308
while((!endOfStream(GET, XXX)) && (totalBytes < SoundSystemConfig.getMaxFileSize()))
309
{
310
bytesRead = 0;
311
cnt = 0;
312
313
try
314
{
315
// Read until small buffer is filled or end of file reached:
316
while(bytesRead < smallBuffer.length)
317
{
318
myAudioInputStream.execute();
319
if((cnt = myAudioInputStream.read(smallBuffer, bytesRead,
320
smallBuffer.length - bytesRead)) < 0)
321
{
322
endOfStream(SET, true);
323
break;
324
}
325
bytesRead += cnt;
326
}
327
} catch (IOException e)
328
{
329
errorMessage("Exception thrown while reading from the "
330
+ "AudioInputStream (location #2).");
331
printStackTrace(e);
332
return null;
333
}
334
335
// Reverse byte order if necessary:
336
// if( reverseBytes )
337
// reverseBytes( smallBuffer, 0, bytesRead );
338
339
// Keep track of the total number of bytes read:
340
totalBytes += bytesRead;
341
342
// Append the small buffer to the full buffer:
343
fullBuffer = appendByteArrays(fullBuffer, smallBuffer, bytesRead);
344
}
345
}
346
347
// Insert the converted data into a ByteBuffer
348
// byte[] data = convertAudioBytes( fullBuffer,
349
// myAudioFormat.getSampleSizeInBits() == 16 );
350
351
// Wrap the data into an SoundBuffer:
352
SoundBuffer soundBuffer = new SoundBuffer(fullBuffer, myAudioFormat);
353
354
// Close the audio input stream
355
try
356
{
357
myAudioInputStream.close();
358
} catch (IOException e)
359
{
360
}
361
362
// Return the result:
363
return soundBuffer;
364
}
365
366
@Override
367
public boolean endOfStream()
368
{
369
return endOfStream(GET, XXX);
370
}
371
372
@Override
373
public void cleanup()
374
{
375
if(myAudioInputStream != null)
376
try
377
{
378
myAudioInputStream.close();
379
} catch (Exception e)
380
{
381
}
382
}
383
384
@Override
385
public AudioFormat getAudioFormat()
386
{
387
return myAudioFormat;
388
}
389
390
/**
391
* Internal method for synchronizing access to the boolean 'initialized'.
392
*
393
* @param action
394
* GET or SET.
395
* @param value
396
* New value if action == SET, or XXX if action == GET.
397
* @return True if steam is initialized.
398
*/
399
private synchronized boolean initialized(boolean action, boolean value)
400
{
401
if(action == SET)
402
initialized = value;
403
return initialized;
404
}
405
406
/**
407
* Internal method for synchronizing access to the boolean 'endOfStream'.
408
*
409
* @param action
410
* GET or SET.
411
* @param value
412
* New value if action == SET, or XXX if action == GET.
413
* @return True if end of stream was reached.
414
*/
415
private synchronized boolean endOfStream(boolean action, boolean value)
416
{
417
if(action == SET)
418
endOfStream = value;
419
return endOfStream;
420
}
421
422
/**
423
* Reverse-orders all bytes contained in the specified array.
424
*
425
* @param buffer
426
* Array containing audio data.
427
*/
428
public static void reverseBytes(byte[] buffer)
429
{
430
reverseBytes(buffer, 0, buffer.length);
431
}
432
433
/**
434
* Reverse-orders the specified range of bytes contained in the specified
435
* array.
436
*
437
* @param buffer
438
* Array containing audio data.
439
* @param offset
440
* Array index to begin.
441
* @param size
442
* number of bytes to reverse-order.
443
*/
444
public static void reverseBytes(byte[] buffer, int offset, int size)
445
{
446
447
byte b;
448
for(int i = offset; i < (offset + size); i += 2)
449
{
450
b = buffer[i];
451
buffer[i] = buffer[i + 1];
452
buffer[i + 1] = b;
453
}
454
}
455
456
/**
457
* Prints an error message.
458
*
459
* @param message
460
* Message to print.
461
*/
462
private void errorMessage(String message)
463
{
464
logger.errorMessage("CodecJLayerMP3", message, 0);
465
}
466
467
/**
468
* Prints an exception's error message followed by the stack trace.
469
*
470
* @param e
471
* Exception containing the information to print.
472
*/
473
private void printStackTrace(Exception e)
474
{
475
logger.printStackTrace(e, 1);
476
}
477
478
/**
479
* Creates a new array with the second array appended to the end of the
480
* first array.
481
*
482
* @param arrayOne
483
* The first array.
484
* @param arrayTwo
485
* The second array.
486
* @param length
487
* How many bytes to append from the second array.
488
* @return Byte array containing information from both arrays.
489
*/
490
private static byte[] appendByteArrays(byte[] arrayOne, byte[] arrayTwo, int length)
491
{
492
byte[] newArray;
493
if(arrayOne == null && arrayTwo == null)
494
{
495
// no data, just return
496
return null;
497
} else if(arrayOne == null)
498
{
499
// create the new array, same length as arrayTwo:
500
newArray = new byte[length];
501
// fill the new array with the contents of arrayTwo:
502
System.arraycopy(arrayTwo, 0, newArray, 0, length);
503
arrayTwo = null;
504
} else if(arrayTwo == null)
505
{
506
// create the new array, same length as arrayOne:
507
newArray = new byte[arrayOne.length];
508
// fill the new array with the contents of arrayOne:
509
System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length);
510
arrayOne = null;
511
} else
512
{
513
// create the new array large enough to hold both arrays:
514
newArray = new byte[arrayOne.length + length];
515
System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length);
516
// fill the new array with the contents of both arrays:
517
System.arraycopy(arrayTwo, 0, newArray, arrayOne.length, length);
518
arrayOne = null;
519
arrayTwo = null;
520
}
521
522
return newArray;
523
}
524
525
private static class DMAISObuffer extends Obuffer
526
{
527
private int m_nChannels;
528
private byte[] m_abBuffer;
529
private int[] m_anBufferPointers;
530
private boolean m_bIsBigEndian;
531
532
public DMAISObuffer(int nChannels)
533
{
534
m_nChannels = nChannels;
535
m_abBuffer = new byte[OBUFFERSIZE * nChannels];
536
m_anBufferPointers = new int[nChannels];
537
reset();
538
}
539
540
public void append(int nChannel, short sValue)
541
{
542
byte bFirstByte;
543
byte bSecondByte;
544
if(m_bIsBigEndian)
545
{
546
bFirstByte = (byte) ((sValue >>> 8) & 0xFF);
547
bSecondByte = (byte) (sValue & 0xFF);
548
} else
549
// little endian
550
{
551
bFirstByte = (byte) (sValue & 0xFF);
552
bSecondByte = (byte) ((sValue >>> 8) & 0xFF);
553
}
554
m_abBuffer[m_anBufferPointers[nChannel]] = bFirstByte;
555
m_abBuffer[m_anBufferPointers[nChannel] + 1] = bSecondByte;
556
m_anBufferPointers[nChannel] += m_nChannels * 2;
557
}
558
559
public void set_stop_flag()
560
{
561
}
562
563
public void close()
564
{
565
}
566
567
public void write_buffer(int nValue)
568
{
569
}
570
571
public void clear_buffer()
572
{
573
}
574
575
public void reset()
576
{
577
for(int i = 0; i < m_nChannels; i++)
578
{
579
/*
580
* Points to byte location, implicitly assuming 16 bit samples.
581
*/
582
m_anBufferPointers[i] = i * 2;
583
}
584
}
585
}
586
}
587
588