/* * JSwiff is an open source Java API for Macromedia Flash file generation * and manipulation * * Copyright (C) 2004-2005 Ralf Terdic (contact@jswiff.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.jswiff.hl.factories; import com.jswiff.SWFDocument; import com.jswiff.SWFWriter; import com.jswiff.io.InputBitStream; import com.jswiff.swfrecords.SoundInfo; import com.jswiff.swfrecords.tags.DefineSound; import com.jswiff.swfrecords.tags.ShowFrame; import com.jswiff.swfrecords.tags.SoundStreamHead2; import com.jswiff.swfrecords.tags.StartSound; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; /** * Creates a document including an uncompressed wav sound file. * * @author <a href="mailto:ralf@terdic.de">Ralf Terdic</a> */ public class WAVDocumentFactory { private static final int WAVE_FORMAT_DESCRIPTOR = 0x45564157; private static final int RIFF_CHUNK_DESCRIPTOR = 0x46464952; private static final int FMT_SUBCHUNK_DESCRIPTOR = 0x20746d66; private static final int DATA_SUBCHUNK_DESCRIPTOR = 0x61746164; private InputBitStream wavBitStream; private byte[] soundData; private SWFDocument doc; private int sampleCount; private int sampleSize; private int channelCount; private int samplingRate; private boolean is16BitSample; /** * <p> * Creates a new WAVDocumentFactory instance. The passed AudioInputStream is * converted to a linear PCM WAV (so you may be able to use other formats * like .au). Sample rates of 5512, 11025, 22050 and 44100 Hz are supported. * </p> * * <p> * Note: you can use the following to get an AudioInputStream: * </p> * <code> AudioInputStream audioInputStream = * AudioSystem.getAudioInputStream((bufferedInputStream)); </code> * * @param audioInputStream audio input stream * * @throws IOException if something went wrong while reading from the audio * stream */ public WAVDocumentFactory(AudioInputStream audioInputStream) throws IOException { audioInputStream = AudioSystem.getAudioInputStream( AudioFormat.Encoding.PCM_SIGNED, audioInputStream); ByteArrayOutputStream baos = new ByteArrayOutputStream(); AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, baos); byte[] buffer = baos.toByteArray(); wavBitStream = new InputBitStream(new ByteArrayInputStream(buffer)); } /** * Creates a new WAVDocumentFactory instance. Sample rates of 5512, 11025, * 22050 and 44100 kHz are supported. * * @param wavStream WAV input stream */ public WAVDocumentFactory(InputStream wavStream) { wavBitStream = new InputBitStream(wavStream); } /** * Returns the created document. * * @return SWF doc * * @throws IOException if an error occured during parsing the wav stream */ public SWFDocument getDocument() throws IOException { initDocument(); return doc; } /** * Changes the sampling rate of an audio input stream. * * @param audioInputStream source stream * @param sampleRate new sampling rate * * @return altered stream */ public static AudioInputStream convertSampleRate( AudioInputStream audioInputStream, float sampleRate) { AudioFormat sourceFormat = audioInputStream.getFormat(); AudioFormat targetFormat = new AudioFormat( sourceFormat.getEncoding(), sampleRate, sourceFormat.getSampleSizeInBits(), sourceFormat.getChannels(), sourceFormat.getFrameSize(), sampleRate, sourceFormat.isBigEndian()); return AudioSystem.getAudioInputStream(targetFormat, audioInputStream); } /** * Main method for quick tests, pass input file (wav) and output file (swf). * * @param args wav, swf files * * @throws IOException if an I/O error occured * @throws UnsupportedAudioFileException */ public static void main(String[] args) throws IOException, UnsupportedAudioFileException { String wavFileName = args[0]; String swfFileName = args[1]; WAVDocumentFactory documentFactory; if (wavFileName.toLowerCase().endsWith("wav")) { documentFactory = new WAVDocumentFactory( new FileInputStream(wavFileName)); } else { documentFactory = new WAVDocumentFactory( AudioSystem.getAudioInputStream( new BufferedInputStream(new FileInputStream(wavFileName), 1024))); } SWFDocument document = documentFactory.getDocument(); SWFWriter writer = new SWFWriter( document, new FileOutputStream(swfFileName)); writer.write(); } private byte getSamplingRateCode() throws IOException { switch (samplingRate) { case 5512: return 0; case 11025: return 1; case 22050: return 2; case 44100: return 3; default: throw new IOException("Unsupported sampling rate: " + samplingRate); } } private void initDocument() throws IOException { doc = new SWFDocument(); doc.setCompressed(true); readData(); doc.addTag( new SoundStreamHead2( SoundStreamHead2.FORMAT_UNCOMPRESSED, getSamplingRateCode(), is16BitSample, (channelCount == 2), 0)); int soundId = doc.getNewCharacterId(); doc.addTag( new DefineSound( soundId, DefineSound.FORMAT_UNCOMPRESSED, getSamplingRateCode(), is16BitSample, (channelCount == 2), sampleCount, soundData)); doc.addTag(new StartSound(soundId, new SoundInfo())); doc.addTag(new ShowFrame()); } private void readData() throws IOException { if (wavBitStream.readUI32() != RIFF_CHUNK_DESCRIPTOR) { throw new IOException( "Illegal WAV format, RIFF chunk descriptor missing!"); } wavBitStream.readUI32(); // chunk size if (wavBitStream.readUI32() != WAVE_FORMAT_DESCRIPTOR) { throw new IOException( "Illegal WAV format, WAVE format descriptor missing!"); } boolean dataSubchunkFound = false; while (!dataSubchunkFound) { long subchunkID = wavBitStream.readUI32(); long subchunkSize = wavBitStream.readUI32(); byte[] subchunkData = wavBitStream.readBytes((int) subchunkSize); if (subchunkID == FMT_SUBCHUNK_DESCRIPTOR) { readFMTSubchunk(subchunkData); } else if (subchunkID == DATA_SUBCHUNK_DESCRIPTOR) { soundData = subchunkData; sampleCount = (int) ((subchunkSize * 8) / (sampleSize * channelCount)); dataSubchunkFound = true; } } } private void readFMTSubchunk(byte[] subchunkData) throws IOException { InputBitStream fmtBitStream = new InputBitStream(subchunkData); int format = fmtBitStream.readUI16(); if (format != 1) { throw new IOException( "Compressed WAV found, only linear quantization (PCM) supported!"); } channelCount = fmtBitStream.readUI16(); samplingRate = (int) fmtBitStream.readUI32(); fmtBitStream.readUI32(); // byte rate fmtBitStream.readUI16(); // block align sampleSize = fmtBitStream.readUI16(); is16BitSample = (sampleSize == 16); // || (chunkFormat != 1) } }