/*
* WAVDecoder.java
* Transform
*
* Copyright (c) 2009-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.util.sound;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.DataFormatException;
import com.flagstone.transform.MovieTag;
import com.flagstone.transform.coder.LittleDecoder;
import com.flagstone.transform.sound.DefineSound;
import com.flagstone.transform.sound.SoundFormat;
import com.flagstone.transform.sound.SoundStreamBlock;
import com.flagstone.transform.sound.SoundStreamHead2;
/**
* Decoder for WAV sounds so they can be added to a flash file.
*/
public final class WAVDecoder implements SoundProvider, SoundDecoder {
/** The binary signature for xIFF files. */
private static final int[] RIFF = {82, 73, 70, 70};
/** The binary signature for WAV files. */
private static final int[] WAV = {87, 65, 86, 69};
/** The identifier of a format block. */
private static final int FMT = 0x20746d66;
/** The identifier of a data block. */
private static final int DATA = 0x61746164;
/** The sound format. */
private transient SoundFormat format;
/** The number of sound channels: 1 - mono, 2 - stereo. */
private transient int numberOfChannels;
/** The number of sound samples for each channel. */
private transient int samplesPerChannel;
/** The rate at which the sound will be played. */
private transient int sampleRate;
/** The number of bytes in each sample. */
private transient int sampleSize;
/** The sound samples. */
private transient byte[] sound = null;
/** The frame rate for the movie. */
private transient float movieRate;
/** The number of bytes already streamed. */
private transient int bytesSent;
/** {@inheritDoc} */
public SoundDecoder newDecoder() {
return new WAVDecoder();
}
/** {@inheritDoc} */
public void read(final File file) throws IOException, DataFormatException {
read(new FileInputStream(file));
}
/** {@inheritDoc} */
public void read(final URL url) throws IOException, DataFormatException {
final URLConnection connection = url.openConnection();
final int fileSize = connection.getContentLength();
if (fileSize < 0) {
throw new FileNotFoundException(url.getFile());
}
read(url.openStream());
}
/** {@inheritDoc} */
public DefineSound defineSound(final int identifier) {
return new DefineSound(identifier, format, sampleRate,
numberOfChannels, sampleSize, samplesPerChannel, sound);
}
/** {@inheritDoc} */
public DefineSound defineSound(final int identifier, final float duration) {
return new DefineSound(identifier, format, sampleRate,
numberOfChannels, sampleSize, samplesPerChannel, sound);
}
/** {@inheritDoc} */
public MovieTag streamHeader(final float frameRate) {
movieRate = frameRate;
return new SoundStreamHead2(format, sampleRate, numberOfChannels,
sampleSize, sampleRate, numberOfChannels, sampleSize,
(int) (sampleRate / frameRate));
}
/** {@inheritDoc} */
public MovieTag streamSound() {
final int samplesPerBlock = (int) (sampleRate / movieRate);
final int bytesPerBlock = samplesPerBlock * sampleSize
* numberOfChannels;
SoundStreamBlock block = null;
if (bytesSent < sound.length) {
final int bytesRemaining = sound.length - bytesSent;
final int numberOfBytes = (bytesRemaining < bytesPerBlock)
? bytesRemaining
: bytesPerBlock;
final byte[] bytes = new byte[numberOfBytes];
System.arraycopy(sound, bytesSent, bytes, 0, numberOfBytes);
block = new SoundStreamBlock(bytes);
bytesSent += numberOfBytes;
}
return block;
}
/** {@inheritDoc} */
public void read(final InputStream stream)
throws IOException, DataFormatException {
final LittleDecoder coder = new LittleDecoder(stream);
for (int i = 0; i < RIFF.length; i++) {
if (coder.readByte() != RIFF[i]) {
throw new DataFormatException("Unsupported format");
}
}
coder.readInt();
for (int i = 0; i < WAV.length; i++) {
if (coder.readByte() != WAV[i]) {
throw new DataFormatException("Unsupported format");
}
}
int chunkType;
int length;
boolean readFMT = false;
boolean readDATA = false;
do {
chunkType = coder.readInt();
length = coder.readInt();
switch (chunkType) {
case FMT:
decodeFMT(coder);
readFMT = true;
break;
case DATA:
decodeDATA(coder, length);
readDATA = true;
break;
default:
coder.skip(length);
break;
}
} while (!(readFMT && readDATA));
}
/**
* Decode the FMT block.
*
* @param coder an SWFDecoder containing the bytes to be decoded.
*
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the block is in a format not supported
* by this decoder.
*/
private void decodeFMT(final LittleDecoder coder)
throws IOException, DataFormatException {
format = SoundFormat.PCM;
if (coder.readUnsignedShort() != 1) {
throw new DataFormatException("Unsupported format");
}
numberOfChannels = coder.readUnsignedShort();
sampleRate = coder.readInt();
coder.readInt(); // total data length
coder.readUnsignedShort(); // total bytes per sample
sampleSize = coder.readUnsignedShort() >> 3;
}
/**
* Decode the Data block containing the sound samples.
*
* @param coder an SWFDecoder containing the bytes to be decoded.
* @param length the length of the block in bytes.
* @throws IOException if there is an error decoding the data.
*/
private void decodeDATA(final LittleDecoder coder, final int length)
throws IOException {
samplesPerChannel = length / (sampleSize * numberOfChannels);
sound = coder.readBytes(new byte[length]);
}
}