/*
* 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.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Creates a document from a mp3 sound file.
*
* @author <a href="mailto:ralf@terdic.de">Ralf Terdic</a>
*/
public class MP3DocumentFactory {
private static final int[][] BIT_RATES = {
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // MPEG2.5
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // reserved
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // MPEG2
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 } // MPEG1
};
private static final int[][] SAMPLING_RATES = {
{11025, 12000, 8000, 0}, // MPEG2.5
{0, 0, 0, 0}, // reserved
{22050, 24000, 16000, 0}, // MPEG2
{44100, 48000, 32000, 0} // MPEG1
};
private static final int[] CHANNELS = { 2, 2, 2, 1 };
private static final int[] SAMPLES_PER_FRAME = { 576, 576, 576, 1152 };
private byte[] mp3Data;
private InputBitStream mp3BitStream;
private byte[] soundData;
private SWFDocument doc;
private int sampleCount;
private int channelCount;
private int samplingRate;
/**
* Creates a new MP3DocumentFactory instance.
*
* @param mp3Stream mp3 stream
*
* @throws IOException if something went wrong while reading from the mp3
* stream
*/
public MP3DocumentFactory(InputStream mp3Stream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1000];
int bytesRead;
while ((bytesRead = mp3Stream.read(buffer)) > 0) {
baos.write(buffer, 0, bytesRead);
}
mp3Data = baos.toByteArray();
mp3BitStream = new InputBitStream(mp3Data);
}
/**
* Returns the created document.
*
* @return SWF doc
*
* @throws IOException if an error occured during parsing the mp3 stream
*/
public SWFDocument getDocument() throws IOException {
initDocument();
return doc;
}
/**
* Main method for quick tests, pass input file (mp3) and output file (swf).
*
* @param args mp3, swf files
*
* @throws IOException if an I/O error occured
*/
public static void main(String[] args) throws IOException {
String mp3FileName = args[0];
String swfFileName = args[1];
MP3DocumentFactory documentFactory = new MP3DocumentFactory(
new FileInputStream(mp3FileName));
SWFDocument document = documentFactory.getDocument();
SWFWriter writer = new SWFWriter(
document, new FileOutputStream(swfFileName));
writer.write();
}
private byte getSamplingRateCode() {
switch (samplingRate) {
case 11025:
return 1;
case 22050:
return 2;
case 44100:
return 3;
default:
return 0;
}
}
private void countSamples() throws IOException {
sampleCount = 0;
while (true) {
int mpegVersion = (int) mp3BitStream.readUnsignedBits(2);
/* int mpegLayer = (int) */ mp3BitStream.readUnsignedBits(2);
mp3BitStream.readBooleanBit(); // CRC
int bitRate = BIT_RATES[mpegVersion][(int) mp3BitStream.readUnsignedBits(
4)];
samplingRate = SAMPLING_RATES[mpegVersion][(int) mp3BitStream.readUnsignedBits(
2)];
if ((bitRate == 0) || (samplingRate == 0)) {
// skip frame
if (findNextFrame()) {
continue;
}
break;
}
int padding = (int) mp3BitStream.readUnsignedBits(1);
mp3BitStream.readBooleanBit(); // reserved
channelCount = CHANNELS[(int) mp3BitStream.readUnsignedBits(2)];
mp3BitStream.readUnsignedBits(6); // ignore 6 bits
sampleCount += SAMPLES_PER_FRAME[mpegVersion];
int frameSize = (((((mpegVersion == 3) ? 144 : 72) * bitRate * 1000) / samplingRate) +
padding) - 4;
mp3BitStream.move(frameSize);
if (!findNextFrame()) {
break;
}
}
}
private boolean findNextFrame() {
// find next frame sync, i.e. 11 set bits
try {
while (true) {
if (mp3BitStream.readUI8() != 0xff) {
continue;
}
long value = mp3BitStream.readUnsignedBits(3);
if (value == 7) {
return true;
}
mp3BitStream.align();
}
} catch (IOException e) {
return false; // end reached
}
}
private void initDocument() throws IOException {
doc = new SWFDocument();
doc.setCompressed(true);
initSoundData();
countSamples();
doc.addTag(
new SoundStreamHead2(
SoundStreamHead2.FORMAT_MP3, getSamplingRateCode(), true,
(channelCount == 2), 0));
int soundId = doc.getNewCharacterId();
doc.addTag(
new DefineSound(
soundId, DefineSound.FORMAT_MP3, getSamplingRateCode(), true,
(channelCount == 2), sampleCount, soundData));
doc.addTag(new StartSound(soundId, new SoundInfo()));
doc.addTag(new ShowFrame());
}
private void initSoundData() throws IOException {
// strip off file header (ID tags etc.)
if (!findNextFrame()) {
throw new IOException("MP3 stream contains no frames!");
}
int offset = (int) (mp3BitStream.getOffset() - 2);
soundData = new byte[mp3Data.length - offset + 2];
soundData[0] = 0;
soundData[1] = 0;
System.arraycopy(mp3Data, offset, soundData, 2, mp3Data.length - offset);
}
}