From db6f121ca8f399fe661e278bc689da8f3e1f7125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=F8=EDk?= Date: Sat, 18 May 2013 16:42:07 +0200 Subject: [PATCH] FLA export - sounds Wav export (for ADPCM sounds) --- .../src/com/jpexs/decompiler/flash/Main.java | 4 +- trunk/src/com/jpexs/decompiler/flash/SWF.java | 99 +++++- .../decompiler/flash/SWFInputStream.java | 9 +- .../decompiler/flash/gui/ExportDialog.java | 2 +- .../jpexs/decompiler/flash/gui/MainFrame.java | 6 +- .../decompiler/flash/tags/DefineSoundTag.java | 10 +- .../flash/tags/RemoveObject2Tag.java | 8 +- .../flash/tags/RemoveObjectTag.java | 8 +- .../flash/tags/SoundStreamHead2Tag.java | 6 +- .../flash/tags/SoundStreamHeadTag.java | 6 +- .../decompiler/flash/tags/base/RemoveTag.java | 26 ++ .../flash/types/sound/AdpcmDecoder.java | 284 ++++++++++++++++++ .../decompiler/flash/xfl/XFLConverter.java | 134 +++++---- 13 files changed, 518 insertions(+), 84 deletions(-) create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/base/RemoveTag.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/types/sound/AdpcmDecoder.java diff --git a/trunk/src/com/jpexs/decompiler/flash/Main.java b/trunk/src/com/jpexs/decompiler/flash/Main.java index 435753e5c..aaf478478 100644 --- a/trunk/src/com/jpexs/decompiler/flash/Main.java +++ b/trunk/src/com/jpexs/decompiler/flash/Main.java @@ -562,7 +562,7 @@ public class Main { System.out.println("Exporting movies..."); exfile.exportMovies(outDir.getAbsolutePath() + File.separator + "movies"); System.out.println("Exporting sounds..."); - exfile.exportSounds(outDir.getAbsolutePath() + File.separator + "sounds", true); + exfile.exportSounds(outDir.getAbsolutePath() + File.separator + "sounds", true, true); System.out.println("Exporting binaryData..."); exfile.exportBinaryData(outDir.getAbsolutePath() + File.separator + "binaryData"); System.out.println("Exporting texts..."); @@ -587,7 +587,7 @@ public class Main { exfile.exportMovies(outDir.getAbsolutePath()); exportOK = true; } else if (exportFormat.equals("sound")) { - exfile.exportSounds(outDir.getAbsolutePath(), true); + exfile.exportSounds(outDir.getAbsolutePath(), true, true); exportOK = true; } else if (exportFormat.equals("binaryData")) { exfile.exportBinaryData(outDir.getAbsolutePath()); diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index 2d4c3525c..8e66a4d2a 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -74,6 +74,7 @@ import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.sound.AdpcmDecoder; import com.jpexs.decompiler.flash.xfl.XFLConverter; import java.io.*; import java.util.ArrayList; @@ -604,12 +605,13 @@ public class SWF { exportMovies(outdir, tags); } - public void exportSounds(String outdir, boolean mp3) throws IOException { - exportSounds(outdir, tags, mp3); + public void exportSounds(String outdir, boolean mp3, boolean wave) throws IOException { + exportSounds(outdir, tags, mp3, wave); } public byte[] exportSound(Tag t) throws IOException { boolean mp3 = true; + boolean wave = true; ByteArrayOutputStream fos = new ByteArrayOutputStream(); int id = 0; if (t instanceof DefineSoundTag) { @@ -619,9 +621,12 @@ public class SWF { if (t instanceof DefineSoundTag) { DefineSoundTag st = (DefineSoundTag) t; - if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { + if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { fos = new ByteArrayOutputStream(); - fos.write(st.soundData); + createWavFromAdpcm(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); + } else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { + fos = new ByteArrayOutputStream(); + fos.write(st.soundData, 2, st.soundData.length - 2); } else { fos = new ByteArrayOutputStream(); FLVOutputStream flv = new FLVOutputStream(fos); @@ -634,7 +639,15 @@ public class SWF { List blocks = new ArrayList(); List objs = new ArrayList(this.tags); populateSoundStreamBlocks(objs, t, blocks); - if ((shead.getSoundFormat() == 2) && mp3) { + if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int b = 0; b < blocks.size(); b++) { + byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION); + baos.write(data); + } + fos = new ByteArrayOutputStream(); + createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); + } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { fos = new ByteArrayOutputStream(); for (int b = 0; b < blocks.size(); b++) { byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION); @@ -658,7 +671,61 @@ public class SWF { return fos.toByteArray(); } - public void exportSounds(String outdir, List tags, boolean mp3) throws IOException { + 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 = val >> 8; + } + } + + private static void createWavFromAdpcm(OutputStream fos, int soundRate, int soundSize, int soundType, byte data[]) throws IOException { + try { + byte pcmData[] = AdpcmDecoder.decode(data, soundType == 1 ? true : false); + + ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream(); + int audioFormat = 1; //PCM + writeLE(subChunk1Data, audioFormat, 2); + int numChannels = soundType == 1 ? 2 : 1; + writeLE(subChunk1Data, numChannels, 2); + int rateMap[] = {5512, 11025, 22050, 44100}; + int sampleRate = rateMap[soundRate]; + writeLE(subChunk1Data, sampleRate, 4); + int bitsPerSample = soundSize == 1 ? 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("fmt ".getBytes()); + byte subChunk1DataBytes[] = subChunk1Data.toByteArray(); + writeLE(chunks, subChunk1DataBytes.length, 4); + chunks.write(subChunk1DataBytes); + + + chunks.write("data".getBytes()); + writeLE(chunks, pcmData.length, 4); + chunks.write(pcmData); + + fos.write("RIFF".getBytes()); + byte chunkBytes[] = chunks.toByteArray(); + writeLE(fos, 4 + chunkBytes.length, 4); + fos.write("WAVE".getBytes()); + fos.write(chunkBytes); + //size1=>16bit*/ + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ex) { + //ignore + } + } + } + } + + public void exportSounds(String outdir, List tags, boolean mp3, boolean wave) throws IOException { if (tags.isEmpty()) { return; } @@ -677,9 +744,13 @@ public class SWF { if (t instanceof DefineSoundTag) { DefineSoundTag st = (DefineSoundTag) t; - if ((st.soundFormat == 2) && mp3) { + + if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { + fos = new FileOutputStream(outdir + File.separator + id + ".wav"); + createWavFromAdpcm(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); + } else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { fos = new FileOutputStream(outdir + File.separator + id + ".mp3"); - fos.write(st.soundData); + fos.write(st.soundData, 2, st.soundData.length - 2); } else { fos = new FileOutputStream(outdir + File.separator + id + ".flv"); FLVOutputStream flv = new FLVOutputStream(fos); @@ -692,11 +763,19 @@ public class SWF { List blocks = new ArrayList(); List objs = new ArrayList(this.tags); populateSoundStreamBlocks(objs, t, blocks); - if ((shead.getSoundFormat() == 2) && mp3) { + if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int b = 0; b < blocks.size(); b++) { + byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION); + baos.write(data); + } + fos = new FileOutputStream(outdir + File.separator + id + ".wav"); + createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); + } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { fos = new FileOutputStream(outdir + File.separator + id + ".mp3"); for (int b = 0; b < blocks.size(); b++) { byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION); - fos.write(data, 4, data.length - 4); + fos.write(data, 2, data.length - 2); } } else { fos = new FileOutputStream(outdir + File.separator + id + ".flv"); diff --git a/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java b/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java index a304802ba..99d14a9ac 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -181,7 +181,7 @@ public class SWFInputStream extends InputStream { return bytesRead; } - private void alignByte() { + public void alignByte() { bitPos = 0; } private int lastPercent = -1; @@ -2974,4 +2974,11 @@ public class SWFInputStream extends InputStream { public int available() throws IOException { return is.available(); } + + public long availableBits() throws IOException { + if (bitPos > 0) { + return available() * 8 + (8 - bitPos); + } + return available() * 8; + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index bcfd73284..76e834e8c 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -22,7 +22,7 @@ public class ExportDialog extends JDialog { {"Plain Text", "Formatted text"}, {"PNG/JPEG"}, {"FLV (No audio)"}, - {"MP3/FLV", "FLV (Audio only)"}, + {"MP3/WAV/FLV", "FLV (Audio only)"}, {"AS", "PCODE"} }; String optionNames[] = { diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java index 8d8b043a4..bccc87f34 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java @@ -1292,7 +1292,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi final String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); Configuration.setConfig("lastExportDir", Helper.fixDialogFile(chooser.getSelectedFile()).getParentFile().getAbsolutePath()); final boolean isPcode = export.getOption(ExportDialog.OPTION_ACTIONSCRIPT) == 1; - final boolean isMp3 = export.getOption(ExportDialog.OPTION_SOUNDS) == 0; + final boolean isMp3OrWav = export.getOption(ExportDialog.OPTION_SOUNDS) == 0; final boolean isFormatted = export.getOption(ExportDialog.OPTION_TEXTS) == 1; final boolean onlySel = e.getActionCommand().endsWith("SEL"); (new Thread() { @@ -1352,7 +1352,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi SWF.exportShapes(selFile + File.separator + "shapes", shapes); swf.exportTexts(selFile + File.separator + "texts", texts, isFormatted); swf.exportMovies(selFile + File.separator + "movies", movies); - swf.exportSounds(selFile + File.separator + "sounds", sounds, isMp3); + swf.exportSounds(selFile + File.separator + "sounds", sounds, isMp3OrWav, isMp3OrWav); swf.exportBinaryData(selFile + File.separator + "binaryData", binaryData); if (abcPanel != null) { for (int i = 0; i < tlsList.size(); i++) { @@ -1375,7 +1375,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi swf.exportShapes(selFile + File.separator + "shapes"); swf.exportTexts(selFile + File.separator + "texts", isFormatted); swf.exportMovies(selFile + File.separator + "movies"); - swf.exportSounds(selFile + File.separator + "sounds", isMp3); + swf.exportSounds(selFile + File.separator + "sounds", isMp3OrWav, isMp3OrWav); swf.exportBinaryData(selFile + File.separator + "binaryData"); swf.exportActionScript(selFile, isPcode); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index 565aea05d..81e42c780 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -96,14 +96,12 @@ public class DefineSoundTag extends CharacterTag { } public String getExportFormat() { - if (soundFormat == FORMAT_MP3) { + if (soundFormat == DefineSoundTag.FORMAT_MP3) { return "mp3"; } + if (soundFormat == DefineSoundTag.FORMAT_ADPCM) { + return "wav"; + } return "flv"; } - - public double getkHz() { - double rateMap[] = {5.5, 11, 22, 44}; - return rateMap[soundRate]; - } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObject2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObject2Tag.java index f85ea4843..b1ff9d2e1 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObject2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObject2Tag.java @@ -17,10 +17,11 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; import java.io.ByteArrayInputStream; import java.io.IOException; -public class RemoveObject2Tag extends Tag { +public class RemoveObject2Tag extends Tag implements RemoveTag { public int depth; @@ -29,4 +30,9 @@ public class RemoveObject2Tag extends Tag { SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(data), version); depth = sis.readUI16(); } + + @Override + public int getDepth() { + return depth; + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObjectTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObjectTag.java index 903ec0546..9d5536068 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObjectTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/RemoveObjectTag.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,7 +29,7 @@ import java.io.OutputStream; * * @author JPEXS */ -public class RemoveObjectTag extends Tag { +public class RemoveObjectTag extends Tag implements RemoveTag { /** * ID of character to place @@ -71,4 +72,9 @@ public class RemoveObjectTag extends Tag { characterId = sis.readUI16(); depth = sis.readUI16(); } + + @Override + public int getDepth() { + return depth; + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java index a8b5bf784..2975ebeb4 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java @@ -18,7 +18,6 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; -import static com.jpexs.decompiler.flash.tags.DefineSoundTag.FORMAT_MP3; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -50,9 +49,12 @@ public class SoundStreamHead2Tag extends CharacterTag implements SoundStreamHead @Override public String getExportFormat() { - if (streamSoundCompression == FORMAT_MP3) { + if (streamSoundCompression == DefineSoundTag.FORMAT_MP3) { return "mp3"; } + if (streamSoundCompression == DefineSoundTag.FORMAT_ADPCM) { + return "wav"; + } return "flv"; } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java index 69c4115ff..ca243d116 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java @@ -18,7 +18,6 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; -import static com.jpexs.decompiler.flash.tags.DefineSoundTag.FORMAT_MP3; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -45,9 +44,12 @@ public class SoundStreamHeadTag extends CharacterTag implements SoundStreamHeadT @Override public String getExportFormat() { - if (streamSoundCompression == FORMAT_MP3) { + if (streamSoundCompression == DefineSoundTag.FORMAT_MP3) { return "mp3"; } + if (streamSoundCompression == DefineSoundTag.FORMAT_ADPCM) { + return "wav"; + } return "flv"; } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/RemoveTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/RemoveTag.java new file mode 100644 index 000000000..2866f4cd3 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/RemoveTag.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 JPEXS + * + * 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 3 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, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +/** + * + * @author JPEXS + */ +public interface RemoveTag { + + public int getDepth(); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/types/sound/AdpcmDecoder.java b/trunk/src/com/jpexs/decompiler/flash/types/sound/AdpcmDecoder.java new file mode 100644 index 000000000..7a00bae1d --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/types/sound/AdpcmDecoder.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2013 JPEXS + * + * 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 3 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, see . + */ +package com.jpexs.decompiler.flash.types.sound; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * + * @author JPEXS + */ +public class AdpcmDecoder { + + private static final int indexAdjustTable2bit[] = { + -1, 2, + -1, 2}; + private static final int indexAdjustTable3bit[] = { + -1, -1, 2, 4, + -1, -1, 2, 4}; + private static final int indexAdjustTable4bit[] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8}; + private static final int indexAdjustTable5bit[] = { + -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16, + -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16}; + private static final int stepSizeTable[] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, + 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, + 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, + 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, + 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, + 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, + 27086, 29794, 32767 + }; + + private static class AdpcmState { + + public int index; + public int sample; + }; + + private static int decode2bit(int deltaCode, AdpcmState state) { + assert (deltaCode == (deltaCode & 3)); + + int step = stepSizeTable[state.index]; + + int difference = step >> 1; + if ((deltaCode & 1) == 1) { + difference += step; + } + if ((deltaCode & 2) == 2) { + difference = -difference; + } + + state.sample += difference; + if (state.sample > 32767) { + state.sample = 32767; + } else if (state.sample < -32768) { + state.sample = -32768; + } + + state.index += indexAdjustTable2bit[deltaCode]; + if (state.index < 0) { + state.index = 0; + } else if (state.index > 88) { + state.index = 88; + } + + return state.sample; + } + + private static int decode3bit(int deltaCode, AdpcmState state) { + assert (deltaCode == (deltaCode & 7)); + + int step = stepSizeTable[state.index]; + + int difference = step >> 2; + if ((deltaCode & 1) == 1) { + difference += step >> 1; + } + if ((deltaCode & 2) == 2) { + difference += step; + } + if ((deltaCode & 4) == 4) { + difference = -difference; + } + + state.sample += difference; + if (state.sample > 32767) { + state.sample = 32767; + } else if (state.sample < -32768) { + state.sample = -32768; + } + + state.index += indexAdjustTable3bit[deltaCode]; + if (state.index < 0) { + state.index = 0; + } else if (state.index > 88) { + state.index = 88; + } + + return state.sample; + } + + private static int decode4bit(int deltaCode, AdpcmState state) { + assert (deltaCode == (deltaCode & 15)); + + int step = stepSizeTable[state.index]; + + int difference = step >> 3; + if ((deltaCode & 1) == 1) { + difference += step >> 2; + } + if ((deltaCode & 2) == 2) { + difference += step >> 1; + } + if ((deltaCode & 4) == 4) { + difference += step; + } + if ((deltaCode & 8) == 8) { + difference = -difference; + } + + state.sample += difference; + if (state.sample > 32767) { + state.sample = 32767; + } else if (state.sample < -32768) { + state.sample = -32768; + } + + state.index += indexAdjustTable4bit[deltaCode]; + if (state.index < 0) { + state.index = 0; + } else if (state.index > 88) { + state.index = 88; + } + + return state.sample; + } + + private static int decode5bit(int deltaCode, AdpcmState state) { + assert (deltaCode >= 0); + assert (deltaCode <= 31 /* 2#11111 */); + + int step = stepSizeTable[state.index]; + + int difference = step >> 4; + if ((deltaCode & 1) == 1) { + difference += step >> 3; + } + if ((deltaCode & 2) == 2) { + difference += step >> 2; + } + if ((deltaCode & 4) == 4) { + difference += step >> 1; + } + if ((deltaCode & 8) == 8) { + difference += step; + } + if ((deltaCode & 16) == 16) { + difference = -difference; + } + + state.sample += difference; + if (state.sample > 32767) { + state.sample = 32767; + } else if (state.sample < -32768) { + state.sample = -32768; + } + + state.index += indexAdjustTable5bit[deltaCode]; + if (state.index < 0) { + state.index = 0; + } else if (state.index > 88) { + state.index = 88; + } + + return state.sample; + } + + public static byte[] decode(byte data[], boolean isStereo) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + decode(new ByteArrayInputStream(data), baos, isStereo); + return baos.toByteArray(); + } + + public static void decode(InputStream is, OutputStream os, boolean is_stereo) throws IOException { + int adpcm_code_size; + SWFInputStream sis = new SWFInputStream(is, SWF.DEFAULT_VERSION); + SWFOutputStream sos = new SWFOutputStream(os, SWF.DEFAULT_VERSION); + adpcm_code_size = (int) sis.readUB(2); + int bits_per_code = adpcm_code_size + 2; + try { + do { + if (is_stereo) { + int initialSampleLeft = (int) sis.readSB(16); + int initialIndexLeft = (int) sis.readUB(6); + int initialSampleRight = (int) sis.readSB(16); + int initialIndexRight = (int) sis.readUB(6); + AdpcmState stateLeft = new AdpcmState(); + stateLeft.index = initialIndexLeft; + stateLeft.sample = initialSampleLeft; + AdpcmState stateRight = new AdpcmState(); + stateRight.index = initialIndexRight; + stateRight.sample = initialSampleRight; + for (int i = 1; (i <= 4095) && (sis.availableBits() >= bits_per_code * 2); i++) { + int codeLeft = (int) sis.readUB(bits_per_code); + int codeRight = (int) sis.readUB(bits_per_code); + int valLeft = 0; + int valRight = 0; + switch (bits_per_code) { + case 2: + valLeft = decode2bit(codeLeft, stateLeft); + valRight = decode2bit(codeRight, stateRight); + break; + case 3: + valLeft = decode3bit(codeLeft, stateLeft); + valRight = decode3bit(codeRight, stateRight); + break; + case 4: + valLeft = decode4bit(codeLeft, stateLeft); + valRight = decode4bit(codeRight, stateRight); + break; + case 5: + valLeft = decode5bit(codeLeft, stateLeft); + valRight = decode5bit(codeRight, stateRight); + break; + } + sos.writeSI16(valLeft); + sos.writeSI16(valRight); + } + } else { + int initialSample = (int) sis.readSB(16); + int initialIndex = (int) sis.readUB(6); + AdpcmState state = new AdpcmState(); + state.index = initialIndex; + state.sample = initialSample; + for (int i = 1; (i <= 4095) && (sis.availableBits() >= bits_per_code); i++) { + int code = (int) sis.readUB(bits_per_code); + int val = 0; + switch (bits_per_code) { + case 2: + val = decode2bit(code, state); + break; + case 3: + val = decode3bit(code, state); + break; + case 4: + val = decode4bit(code, state); + break; + case 5: + val = decode5bit(code, state); + break; + } + sos.writeSI16(val); + } + } + } while (sis.available() > 0); + } catch (EndOfStreamException eos) { + } + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/trunk/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index de7159848..83dc484d6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/trunk/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -48,6 +48,7 @@ import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; @@ -605,7 +606,7 @@ public class XFLConverter { for (Tag t : tags) { if (t instanceof SoundStreamHeadTypeTag) { SoundStreamHeadTypeTag ssh = (SoundStreamHeadTypeTag) t; - ssh.setVirtualCharacterId(maxId++); + ssh.setVirtualCharacterId(++maxId); } if (t instanceof CharacterTag) { CharacterTag ct = (CharacterTag) t; @@ -1149,7 +1150,7 @@ public class XFLConverter { int soundSize = 0; long soundSampleCount = 0; byte soundData[] = new byte[0]; - double rateMap[] = {5.5, 11, 22, 44}; + int rateMap[] = {5, 11, 22, 44}; String exportFormat = "flv"; if (symbol instanceof SoundStreamHeadTypeTag) { SoundStreamHeadTypeTag sstream = (SoundStreamHeadTypeTag) symbol; @@ -1206,7 +1207,7 @@ public class XFLConverter { } if (soundFormat == DefineSoundTag.FORMAT_ADPCM) { SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(soundData), SWF.DEFAULT_VERSION); - + exportFormat = "wav"; try { int adpcmCodeSize = (int) sis.readUB(2); bits = 2 + adpcmCodeSize; @@ -1278,7 +1279,7 @@ public class XFLConverter { String symbolFile = "sound" + symbol.getCharacterID() + "." + exportFormat; files.put(symbolFile, data); String mediaLinkStr = " characters, List tags) { + String ret = ""; + StartSoundTag lastStartSound = null; + SoundStreamHeadTypeTag lastSoundStreamHead = null; + StartSoundTag startSound = null; + SoundStreamHeadTypeTag soundStreamHead = null; + int duration = 1; + int frame = 0; + for (Tag t : tags) { + if (t instanceof StartSoundTag) { + startSound = (StartSoundTag) t; + } + if (t instanceof SoundStreamHeadTypeTag) { + soundStreamHead = (SoundStreamHeadTypeTag) t; + } + if (t instanceof ShowFrameTag) { + if (soundStreamHead != null || startSound != null) { + if (lastSoundStreamHead != null || lastStartSound != null) { + ret += convertFrame(characters, tags, lastSoundStreamHead, lastStartSound, frame, null, false, duration, "", ""); + } + frame += duration; + duration = 1; + lastSoundStreamHead = soundStreamHead; + lastStartSound = startSound; + soundStreamHead = null; + startSound = null; + } else { + duration++; + } + } + } + if (lastSoundStreamHead != null || lastStartSound != null) { + if (frame < 0) { + frame = 0; + duration = 1; + } + ret += convertFrame(characters, tags, lastSoundStreamHead, lastStartSound, frame, null, false, duration, "", ""); + } + if (!ret.equals("")) { + ret = "" + + "" + ret + "" + + ""; + } + return ret; + } + public static String convertTimeline(String initActionScript, List oneInstanceShapes, String backgroundColor, List tags, HashMap characters, String name) { String ret = ""; ret += ""; @@ -1657,6 +1677,10 @@ public class XFLConverter { } + + int soundLayerIndex = layerCount; + ret += convertSoundLayer(soundLayerIndex, backgroundColor, characters, tags); + ret += ""; ret += ""; return ret;