/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.types.sound; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; /** * * @author JPEXS */ public class SoundFormat { public int formatId; public int samplingRate; public boolean stereo; //int[] rateMap = {5512, 11025, 22050, 44100}; public static final int FORMAT_UNCOMPRESSED_NATIVE_ENDIAN = 0; public static final int FORMAT_ADPCM = 1; public static final int FORMAT_MP3 = 2; public static final int FORMAT_UNCOMPRESSED_LITTLE_ENDIAN = 3; public static final int FORMAT_NELLYMOSER16KHZ = 4; public static final int FORMAT_NELLYMOSER8KHZ = 5; public static final int FORMAT_NELLYMOSER = 6; public static final int FORMAT_SPEEX = 11; public SoundFormat() { } public SoundExportFormat getNativeExportFormat() { switch (formatId) { case FORMAT_UNCOMPRESSED_NATIVE_ENDIAN: case FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: case FORMAT_ADPCM: return SoundExportFormat.WAV; case FORMAT_MP3: return SoundExportFormat.MP3; case FORMAT_NELLYMOSER16KHZ: case FORMAT_NELLYMOSER8KHZ: case FORMAT_NELLYMOSER: case FORMAT_SPEEX: return SoundExportFormat.FLV; default: return SoundExportFormat.FLV; } } public SoundFormat(int formatId, int samplingRate, boolean stereo) { this.formatId = formatId; this.samplingRate = samplingRate; this.stereo = stereo; ensureFormat(); } public byte[] decode(SWFInputStream sis) { try { return getDecoder().decode(sis); } catch (IOException ex) { return null; } } public boolean decode(SWFInputStream sis, OutputStream os) { try { getDecoder().decode(sis, os); return true; } catch (IOException ex) { return false; } } public boolean play(SWFInputStream sis) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (!decode(sis, baos)) { return false; } AudioFormat audioFormat = new AudioFormat(samplingRate, 16, stereo ? 2 : 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) { line.open(audioFormat); byte[] outData = baos.toByteArray(); line.write(outData, 0, outData.length); line.drain(); line.stop(); return true; } catch (LineUnavailableException ex) { return false; } } private void ensureFormat() { switch (formatId) { case FORMAT_NELLYMOSER16KHZ: samplingRate = 16000; break; case FORMAT_NELLYMOSER8KHZ: samplingRate = 8000; break; case FORMAT_NELLYMOSER: samplingRate = 22050; break; } } public SoundDecoder getDecoder() { ensureFormat(); switch (formatId) { case FORMAT_UNCOMPRESSED_NATIVE_ENDIAN: case FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: return new NoDecoder(this); case FORMAT_ADPCM: return new AdpcmDecoder(this); case FORMAT_MP3: return new MP3Decoder(this); case FORMAT_NELLYMOSER16KHZ: return new NellyMoserDecoder(this); case FORMAT_NELLYMOSER8KHZ: return new NellyMoserDecoder(this); case FORMAT_NELLYMOSER: return new NellyMoserDecoder(this); case FORMAT_SPEEX: return null; //I haven't seen any Speex audio in the wild default: return null; } } public String getFormatName() { switch (formatId) { case FORMAT_UNCOMPRESSED_NATIVE_ENDIAN: return "Uncompressed native endian"; case FORMAT_ADPCM: return "ADPCM"; case FORMAT_MP3: return "MP3"; case FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: return "Uncompressed little endian"; case FORMAT_NELLYMOSER16KHZ: return "NellyMoser 16kHz"; case FORMAT_NELLYMOSER8KHZ: return "NellyMoser 8kHz"; case FORMAT_NELLYMOSER: return "NellyMoser"; case FORMAT_SPEEX: return "Speex"; } return null; } private static void writeLE(OutputStream os, long val, int size) throws IOException { for (int i = 0; i < size; i++) { os.write((int) (val & 0xff)); val >>= 8; } } public boolean createWav(List<ByteArrayRange> dataRanges, OutputStream os) throws IOException { ensureFormat(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); SoundDecoder decoder = getDecoder(); for (ByteArrayRange dataRange : dataRanges) { SWFInputStream sis = new SWFInputStream(null, dataRange.getArray(), 0, dataRange.getPos() + dataRange.getLength()); sis.seek(dataRange.getPos()); decoder.decode(sis, baos); } try { createWavFromPcmData(os, samplingRate, true, stereo, baos.toByteArray()); return true; } catch (IOException ex) { return false; } } public static void createWavFromPcmData(OutputStream fos, int soundRateHz, boolean sample16bit, boolean stereo, byte[] data) throws IOException { ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream(); int audioFormat = 1; //PCM writeLE(subChunk1Data, audioFormat, 2); int numChannels = stereo ? 2 : 1; writeLE(subChunk1Data, numChannels, 2); int sampleRate = soundRateHz;//rateMap[soundRate]; writeLE(subChunk1Data, sampleRate, 4); int bitsPerSample = sample16bit ? 16 : 8; int byteRate = sampleRate * numChannels * bitsPerSample / 8; writeLE(subChunk1Data, byteRate, 4); int blockAlign = numChannels * bitsPerSample / 8; writeLE(subChunk1Data, blockAlign, 2); writeLE(subChunk1Data, bitsPerSample, 2); ByteArrayOutputStream chunks = new ByteArrayOutputStream(); chunks.write(Utf8Helper.getBytes("fmt ")); byte[] subChunk1DataBytes = subChunk1Data.toByteArray(); writeLE(chunks, subChunk1DataBytes.length, 4); chunks.write(subChunk1DataBytes); chunks.write(Utf8Helper.getBytes("data")); writeLE(chunks, data.length, 4); chunks.write(data); fos.write(Utf8Helper.getBytes("RIFF")); byte[] chunkBytes = chunks.toByteArray(); writeLE(fos, 4 + chunkBytes.length, 4); fos.write(Utf8Helper.getBytes("WAVE")); fos.write(chunkBytes); } }