Show Audio Information While Playing Sound #77
Chapter 10, Audio
|
397
HACK
Hacking the Hack
Florian’s message to javasound-interest says it is best if “everybody does
the calculation of the ‘level’ on his own[, based] on the buffers received by
the TDL [(
TargetDataLine
, usually used by capture devices)] or written to
the SDL [(
SourceDataLine
)], respectively.”
Setting aside the argument of duplication of effort, note that the buffers he
speaks of are available in the hack code; it’s what the
PCMFilePlayer reads
from the file and writes to the
Line (specifically, a SourceDataLine, as
Florian’s message notes). So, in theory at least, this can be done. But it’s not
going to be pretty.
First, create a new
DataLineInfoGUI2 class that is identical to the one from
earlier in this hack, except that instead of using a
PCMFilePlayer, it uses a
PCMFilePlayerLeveler, a class that will be defined next.
This new class is pretty much the same as the old
PCMFilePlayer, except that
on each time through the while loop, as it reads the buffer and writes it to
the line, it will call a method to scan through the buffer and determine a
level for this group of samples. So, after reading the bytes from the input
stream but before writing them to the line, add:
// calculate level
calculateLevel (buffer, readPoint, leftover);
As Florian argues in his message, the idea of a level is up for interpretation,
but there is a general sense that it should represent the loudness or quiet-
ness of the audio at a certain time. Making the problem worse is the fact that
the sample values will always be going up and down because the samples
represent how much a speaker should be excited or relaxed, and it’s the
sample’s periodic change that creates sound waves we hear. Put another
way, even the loudest sounds can have some 0 samples at the bottom of
their waves.
As a crude attempt at approximating a level, this hack’s implementation gets
the maximum amplitude (on either speaker, if the source is stereo) in the
entire buffer. To make this a little more fine-tuned, this version of the player
figures out a buffer size suitable to provide 1/20 of a second of audio, rather
than the flat 32 KB used earlier. To do that, add this after getting the
Line in
the constructor:
// figure out a small buffer size
int bytesPerSec = format.getSampleSizeInBits( ) *
(int) format.getSampleRate( );
System.out.println ("bytesPerSec = " + bytesPerSec);
int bufferSize = bytesPerSec / 20;
buffer = new byte[bufferSize];
398
|
Chapter 10, Audio
#77 Show Audio Information While Playing Sound
HACK
This needs to sync with the line as well—if the line’s buffer is nearly full, it
won’t accept this entire buffer on the
write( ) without blocking. So, you can
tune the while loop to do a read-and-write only if the
Line
will accept a buff-
erful of data. Do this by adding the following block after the
if
(playing)
statement:
// only write if the line will take at
// least a buffer-ful of data
if (line.available( ) < buffer.length) {
Thread.yield( );
continue;
}
Now, the only problem is implementing calculateLevel( )—i.e., doing the
actual iteration through the buffer to calculate a maximum value. This,
frankly, is a huge pain in the butt, because to determine each sample value,
you have to deal with four issues you hadn’t cared about before:
Channels (i.e., mono versus stereo)
Sample size
Endianness
Signing
This is handled in the
calculateLevel( ) method of PCMFilePlayerLeveler,
listed in Example 10-11.
Example 10-11. Method to calculate a crude “level” of sample bytes in a buffer
private void calculateLevel (byte[] buffer,
int readPoint,
int leftOver) {
int max = 0;
boolean use16Bit = (format.getSampleSizeInBits( ) == 16);
boolean signed = (format.getEncoding( ) ==
AudioFormat.Encoding.PCM_SIGNED);
boolean bigEndian = (format.isBigEndian( ));
if (use16Bit) {
for (int i=readPoint; i<buffer.length-leftOver; i+=2) {
int value = 0;
// deal with endianness
int hiByte = (bigEndian ? buffer[i] : buffer[i+1]);
int loByte = (bigEndian ? buffer[i+1] : buffer [i]);
if (signed) {
short shortVal = (short) hiByte;
shortVal = (short) ((shortVal << 8) | (byte) loByte);
value = shortVal;
} else {
value = (hiByte << 8) | loByte;
}

Get Swing Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.