/* * 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]); } }