diff --git a/trunk/lib/jl1.0.1.jar b/trunk/lib/jl1.0.1.jar new file mode 100644 index 000000000..caaf140d4 Binary files /dev/null and b/trunk/lib/jl1.0.1.jar differ diff --git a/trunk/nbproject/project.xml b/trunk/nbproject/project.xml index 9ef98a8b3..21528e0a9 100644 --- a/trunk/nbproject/project.xml +++ b/trunk/nbproject/project.xml @@ -156,7 +156,7 @@ src - lib/LZMA.jar;lib/jna-3.5.1.jar;lib/jpproxy.jar;lib/jsyntaxpane-0.9.5.jar;lib/trident-6.2.jar;lib/substance-flamingo-6.2.jar;lib/flamingo-6.2.jar;lib/substance-6.2.jar + lib/LZMA.jar;lib/jna-3.5.1.jar;lib/jpproxy.jar;lib/jsyntaxpane-0.9.5.jar;lib/trident-6.2.jar;lib/substance-flamingo-6.2.jar;lib/flamingo-6.2.jar;lib/substance-6.2.jar;lib/jl1.0.1.jar build javadoc reports diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index 4cbf6104e..598b32b60 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -1181,28 +1181,6 @@ public final class SWF implements TreeItem, Timelined { return false; } - public static void populateSoundStreamBlocks(List tags, Tag head, List output) { - boolean found = false; - for (ContainerItem t : tags) { - if (t == head) { - found = true; - continue; - } - if (!found) { - continue; - } - if (t instanceof SoundStreamBlockTag) { - output.add((SoundStreamBlockTag) t); - } - if (t instanceof SoundStreamHeadTypeTag) { - break; - } - if (t instanceof Container) { - populateSoundStreamBlocks(((Container) t).getSubItems(), head, output); - } - } - } - public void populateVideoFrames(int streamId, List tags, HashMap output) { for (ContainerItem t : tags) { if (t instanceof VideoFrameTag) { @@ -1229,7 +1207,10 @@ public final class SWF implements TreeItem, Timelined { if (t instanceof DefineSoundTag) { DefineSoundTag st = (DefineSoundTag) t; - if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { + if ((st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { + //Does endiannes matter here? + createWavFromPcmData(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); + } else if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { createWavFromAdpcm(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); } else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { fos.write(st.soundData, 2, st.soundData.length - 2); @@ -1241,8 +1222,7 @@ public final class SWF implements TreeItem, Timelined { } if (t instanceof SoundStreamHeadTypeTag) { SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t; - List blocks = new ArrayList<>(); - populateSoundStreamBlocks(this.tags, t, blocks); + List blocks = shead.getBlocks(); if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int b = 0; b < blocks.size(); b++) { @@ -1250,6 +1230,14 @@ public final class SWF implements TreeItem, Timelined { baos.write(data); } createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); + } + if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int b = 0; b < blocks.size(); b++) { + byte[] data = blocks.get(b).getData(); + baos.write(data); + } + createWavFromPcmData(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { for (int b = 0; b < blocks.size(); b++) { byte[] data = blocks.get(b).getData(); @@ -1280,9 +1268,7 @@ public final class SWF implements TreeItem, Timelined { } } - private static void createWavFromAdpcm(OutputStream fos, int soundRate, boolean soundSize, boolean soundType, byte[] data) throws IOException { - byte[] pcmData = AdpcmDecoder.decode(data, soundType); - + private static void createWavFromPcmData(OutputStream fos, int soundRate, boolean soundSize, boolean soundType, byte[] data) throws IOException { ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream(); int audioFormat = 1; //PCM writeLE(subChunk1Data, audioFormat, 2); @@ -1305,15 +1291,18 @@ public final class SWF implements TreeItem, Timelined { chunks.write(subChunk1DataBytes); chunks.write(Utf8Helper.getBytes("data")); - writeLE(chunks, pcmData.length, 4); - chunks.write(pcmData); + 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); - //size1=>16bit*/ + } + + private static void createWavFromAdpcm(OutputStream fos, int soundRate, boolean soundSize, boolean soundType, byte[] data) throws IOException { + createWavFromPcmData(fos, soundRate, soundSize, soundType, AdpcmDecoder.decode(data, soundType)); } public List exportSounds(AbortRetryIgnoreHandler handler, String outdir, List tags, boolean mp3, boolean wave) throws IOException { @@ -1339,7 +1328,19 @@ public final class SWF implements TreeItem, Timelined { if (t instanceof DefineSoundTag) { final DefineSoundTag st = (DefineSoundTag) t; - if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { + if (st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + //Does endiannes matter here? + final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".wav"); + newfile = file; + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { + createWavFromPcmData(os, st.soundRate, st.soundSize, st.soundType, st.soundData); + } + } + }, handler).run(); + } else if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".wav"); newfile = file; new RetryTask(new RunnableIOEx() { @@ -1378,11 +1379,26 @@ public final class SWF implements TreeItem, Timelined { } if (t instanceof SoundStreamHeadTypeTag) { final SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t; - final List blocks = new ArrayList<>(); + final List blocks = ((SoundStreamHeadTypeTag) t).getBlocks(); List objs = new ArrayList<>(); objs.addAll(this.tags); - populateSoundStreamBlocks(objs, t, blocks); - if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { + if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int b = 0; b < blocks.size(); b++) { + byte[] data = blocks.get(b).getData(); + baos.write(data); + } + final File file = new File(outdir + File.separator + id + ".wav"); + newfile = file; + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { + createWavFromPcmData(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); + } + } + }, handler).run(); + } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int b = 0; b < blocks.size(); b++) { byte data[] = blocks.get(b).getData(); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 7ff3d7464..34305035c 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -19,7 +19,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.AppStrings; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.exporters.Matrix; -import com.jpexs.decompiler.flash.gui.player.FlashDisplay; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; @@ -56,7 +56,7 @@ import javax.swing.JColorChooser; import javax.swing.JLabel; import javax.swing.JPanel; -public final class ImagePanel extends JPanel implements ActionListener, FlashDisplay { +public final class ImagePanel extends JPanel implements ActionListener, MediaDisplay { static final String ACTION_SELECT_BKCOLOR = "SELECTCOLOR"; diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java index d9d1ad2fb..3d3f97a06 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -36,6 +36,7 @@ import com.jpexs.decompiler.flash.gui.abc.LineMarkedEditorPane; import com.jpexs.decompiler.flash.gui.abc.treenodes.TreeElement; import com.jpexs.decompiler.flash.gui.action.ActionPanel; import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; import com.jpexs.decompiler.flash.gui.player.PlayerControls; import com.jpexs.decompiler.flash.gui.timeline.TimelineFrame; import com.jpexs.decompiler.flash.gui.treenodes.SWFBundleNode; @@ -118,6 +119,10 @@ import com.jpexs.decompiler.flash.xfl.FLAVersion; import com.jpexs.decompiler.graph.ExportMode; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import com.jpexs.helpers.sound.MP3Player; +import com.jpexs.helpers.sound.SoundPlayer; +import com.jpexs.helpers.sound.WavPlayer; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; @@ -145,9 +150,11 @@ import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -196,6 +203,7 @@ import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; +import javazoom.jl.decoder.JavaLayerException; /** * @@ -272,6 +280,144 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec public TreeNode oldNode; public TreeItem oldTag; private File tempFile; + private PlayerControls imagePlayControls; + + private SoundThread soundThread = null; + + private class SoundThread implements MediaDisplay { + + private SoundPlayer player; + + private int mp3SampleCount; + private boolean mp3; + private Thread thr; + private int actualPos = 0; + private boolean playing = false; + private int mp3FrameRate; + private byte data[]; + + private static final int FRAME_DIVISOR = 1024; + + public SoundThread(byte data[]) { + this(data, false, 0, 0); + } + + public SoundThread(byte data[], boolean mp3, int mp3SampleCount, int mp3FrameRate) { + this.data = data; + this.mp3SampleCount = mp3SampleCount; + this.mp3 = mp3; + this.mp3FrameRate = mp3FrameRate; + + } + + @Override + public synchronized int getCurrentFrame() { + if (player == null) { + return 0; + } + + if (!playing) { + return actualPos; + } + + actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); + return actualPos; + } + + @Override + public synchronized int getTotalFrames() { + //System.out.println("getTotalFrames"); + if (player == null) { + return 0; + } + int ret = (int) (player.samplesCount() / FRAME_DIVISOR); + + //System.out.println("/getTotalFrames"); + return ret; + } + + @Override + public synchronized void pause() { + if (!playing) { + return; + } + playing = false; + actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); + player.stop(); + + } + + @Override + public synchronized void play() { + if (player != null) { + player.stop(); + } + + if (mp3) { + try { + player = new MP3Player(new ByteArrayInputStream(data), mp3SampleCount, mp3FrameRate); + } catch (JavaLayerException ex) { + Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + player = new WavPlayer(new ByteArrayInputStream(data)); + } + final int startPos = actualPos * FRAME_DIVISOR; + thr = new Thread() { + + @Override + public void run() { + player.skip(startPos); + player.play(); + if (playing) { + gotoFrame(0); + play(); + } + } + + }; + thr.start(); + playing = true; + } + + @Override + public void rewind() { + actualPos = 0; + } + + @Override + public boolean isPlaying() { + return playing; + } + + @Override + public synchronized void gotoFrame(int frame) { + if (playing) { + playing = false; + player.stop(); + } + actualPos = frame; + } + + @Override + public void setBackground(Color color) { + + } + + @Override + public int getFrameRate() { + if (player == null) { + return 1; + } + return (int) (player.getFrameRate() / FRAME_DIVISOR); + } + + @Override + public boolean isLoaded() { + return true; + } + + } private static final String ACTION_SELECT_BKCOLOR = "SELECTCOLOR"; private static final String ACTION_REPLACE_IMAGE = "REPLACEIMAGE"; @@ -739,8 +885,24 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec if (flashPanel != null) { JPanel flashPlayPanel = new JPanel(new BorderLayout()); flashPlayPanel.add(flashPanel, BorderLayout.CENTER); - flashPlayPanel.add(flashControls = new PlayerControls(flashPanel), BorderLayout.SOUTH); - leftComponent = flashPlayPanel; + + JPanel bottomPanel = new JPanel(new BorderLayout()); + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton selectColorButton = new JButton(View.getIcon("color16")); + selectColorButton.addActionListener(this); + selectColorButton.setActionCommand(ACTION_SELECT_BKCOLOR); + selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint")); + buttonsPanel.add(selectColorButton); + bottomPanel.add(buttonsPanel, BorderLayout.EAST); + + flashPlayPanel.add(bottomPanel, BorderLayout.SOUTH); + + JPanel flashPlayPanel2 = new JPanel(new BorderLayout()); + flashPlayPanel2.add(flashPlayPanel, BorderLayout.CENTER); + flashPlayPanel2.add(flashControls = new PlayerControls(flashPanel), BorderLayout.SOUTH); + + //pan.add(bottomPanel, BorderLayout.SOUTH); + leftComponent = flashPlayPanel2; } else { JPanel swtPanel = new JPanel(new BorderLayout()); swtPanel.add(new JLabel("
" + translate("notavailonthisplatform") + "
", JLabel.CENTER), BorderLayout.CENTER); @@ -782,15 +944,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec ((CardLayout) viewerCards.getLayout()).show(viewerCards, FLASH_VIEWER_CARD); if (flashPanel != null) { - JPanel bottomPanel = new JPanel(new BorderLayout()); - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton selectColorButton = new JButton(View.getIcon("color16")); - selectColorButton.addActionListener(this); - selectColorButton.setActionCommand(ACTION_SELECT_BKCOLOR); - selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint")); - buttonsPanel.add(selectColorButton); - bottomPanel.add(buttonsPanel, BorderLayout.EAST); - pan.add(bottomPanel, BorderLayout.SOUTH); + } pan.add(leftComponent, BorderLayout.CENTER); viewerCards.add(pan, FLASH_VIEWER_CARD); @@ -816,7 +970,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec JPanel previewCnt = new JPanel(new BorderLayout()); previewCnt.add(previewImagePanel, BorderLayout.CENTER); - previewCnt.add(new PlayerControls(previewImagePanel), BorderLayout.SOUTH); + previewCnt.add(imagePlayControls = new PlayerControls(previewImagePanel), BorderLayout.SOUTH); previewPanel.add(previewCnt, BorderLayout.CENTER); JLabel prevIntLabel = new HeaderLabel(translate("swfpreview.internal")); prevIntLabel.setHorizontalAlignment(SwingConstants.CENTER); @@ -2364,6 +2518,10 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec } swfPreviewPanel.stop(); imagePanel.stop(); + if (soundThread != null) { + soundThread.pause(); + } + imagePlayControls.setMedia(imagePanel); stopFlashPlayer(); if (tagObj instanceof ScriptPack) { final ScriptPack scriptLeaf = (ScriptPack) tagObj; @@ -2515,8 +2673,42 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec timelined = ((DefineSpriteTag) fn.getParent()); } previewImagePanel.setTimelined(timelined, swf, fn.getFrame() - 1); - //previewImagePanel.setImage(SWF.frameToImageGet(timeline, fn.getFrame() - 1, null,0,rect, Matrix.getScaleInstance(1 / SWF.unitDivisor), new ColorTransform())); - } else if (((tagObj instanceof FrameNodeItem) && ((FrameNodeItem) tagObj).isDisplayed()) || ((tagObj instanceof CharacterTag) || (tagObj instanceof FontTag)) && (tagObj instanceof Tag)) { + } else if (((tagObj instanceof DefineSoundTag) && mainMenu.isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((DefineSoundTag) tagObj).getExportFormat()))) + || ((tagObj instanceof SoundStreamHeadTypeTag) && mainMenu.isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundStreamHeadTypeTag) tagObj).getExportFormat())))) { + showCard(CARDDRAWPREVIEWPANEL); + previewImagePanel.setImage(new SerializableImage(1, 1, BufferedImage.TYPE_INT_ARGB)); + + String exportFormat = ""; + int soundRate = 0; + long sampleCount = 0; + boolean soundType = false; + if (tagObj instanceof DefineSoundTag) { + DefineSoundTag ds = (DefineSoundTag) tagObj; + exportFormat = ds.getExportFormat(); + soundRate = ds.soundRate; + sampleCount = ds.soundSampleCount; + soundType = ds.soundType; + } + if (tagObj instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag sh = (SoundStreamHeadTypeTag) tagObj; + exportFormat = sh.getExportFormat(); + soundRate = sh.getSoundRate(); + sampleCount = sh.getSoundSampleCount() * sh.getBlocks().size(); + soundType = sh.getSoundType(); + } + try { + byte sounddata[] = tagObj.getSwf().exportSound((Tag) tagObj); + final int soundRates[] = new int[]{5512, 11025, 22050, 44100}; + int frameRate = soundRates[soundRate]; + soundThread = new SoundThread(sounddata, exportFormat.equals("mp3"), (int) (sampleCount * (soundType ? 2 : 1)), frameRate * (soundType ? 2 : 1)); + imagePlayControls.setMedia(soundThread); + soundThread.play(); + + } catch (IOException ex) { + Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, "Cannot export Sound for playback", ex); + } + + } else if (((tagObj instanceof FrameNodeItem) && ((FrameNodeItem) tagObj).isDisplayed()) || ((tagObj instanceof CharacterTag) || (tagObj instanceof FontTag)) && (tagObj instanceof Tag) || (tagObj instanceof SoundStreamHeadTypeTag)) { ((CardLayout) viewerCards.getLayout()).show(viewerCards, FLASH_VIEWER_CARD); createAndShowTempSwf(tagObj); @@ -2582,7 +2774,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec List soundFrames = new ArrayList<>(); if (tagObj instanceof SoundStreamHeadTypeTag) { - SWF.populateSoundStreamBlocks(swf.tags, (Tag) tagObj, soundFrames); + soundFrames = ((SoundStreamHeadTypeTag) tagObj).getBlocks(); frameCount = soundFrames.size(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/TagTreeModel.java b/trunk/src/com/jpexs/decompiler/flash/gui/TagTreeModel.java index 75d708ef2..6815f8cd2 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/TagTreeModel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/TagTreeModel.java @@ -164,8 +164,7 @@ public class TagTreeModel implements TreeModel { for (int i = 0; i < sounds.size(); i++) { if (sounds.get(i).getItem() instanceof SoundStreamHeadTypeTag) { - List blocks = new ArrayList<>(); - SWF.populateSoundStreamBlocks(list, (Tag) sounds.get(i).getItem(), blocks); + List blocks = ((SoundStreamHeadTypeTag) sounds.get(i).getItem()).getBlocks(); if (blocks.isEmpty()) { sounds.remove(i); i--; diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java index c4c2cbbbc..08b65cdb2 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java @@ -45,7 +45,7 @@ import java.util.concurrent.TimeoutException; * * @author JPEXS */ -public class FlashPlayerPanel extends Panel implements FlashDisplay { +public class FlashPlayerPanel extends Panel implements MediaDisplay { private boolean executed = false; private String flash; diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashDisplay.java b/trunk/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java similarity index 93% rename from trunk/src/com/jpexs/decompiler/flash/gui/player/FlashDisplay.java rename to trunk/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java index 2bff3a154..7190aa584 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/player/FlashDisplay.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java @@ -22,7 +22,7 @@ import java.awt.Color; * * @author JPEXS */ -public interface FlashDisplay { +public interface MediaDisplay { public int getCurrentFrame(); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java b/trunk/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java index 993547465..a00cb970b 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java @@ -46,7 +46,7 @@ public class PlayerControls extends JPanel implements ActionListener { private final JButton pauseButton; private boolean paused = false; - private final FlashDisplay display; + private MediaDisplay display; private JProgressBar progress; private final Timer timer; private final JLabel timeLabel; @@ -54,7 +54,7 @@ public class PlayerControls extends JPanel implements ActionListener { private static final Icon pauseIcon = View.getIcon("pause16"); private static final Icon playIcon = View.getIcon("play16"); - public PlayerControls(final FlashDisplay display) { + public PlayerControls(MediaDisplay display) { this.display = display; JPanel controlPanel = new JPanel(new BorderLayout()); timeLabel = new JLabel("00:00.00"); @@ -81,14 +81,15 @@ public class PlayerControls extends JPanel implements ActionListener { Dimension pref = progress.getPreferredSize(); pref.height = 20; progress.setPreferredSize(pref); + final PlayerControls t = this; progress.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { - int frame = (int) Math.floor(e.getX() * display.getTotalFrames() / (double) progress.getWidth()); + int frame = (int) Math.floor(e.getX() * t.display.getTotalFrames() / (double) progress.getWidth()); boolean p = paused; - display.gotoFrame(frame); + t.display.gotoFrame(frame); if (!p) { - display.play(); + t.display.play(); } } }); @@ -121,6 +122,10 @@ public class PlayerControls extends JPanel implements ActionListener { return ret; } + public void setMedia(MediaDisplay media) { + this.display = media; + } + private void update() { View.execInEventDispatch(new Runnable() { @Override diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index 61c0879d8..c3e245d7f 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -115,6 +115,12 @@ public class DefineSoundTag extends CharacterTag { if (soundFormat == DefineSoundTag.FORMAT_ADPCM) { return "wav"; } + if (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return "wav"; + } + if (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return "wav"; + } return "flv"; } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java index 085b0cbf9..061ef60aa 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java @@ -30,6 +30,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; /** * @@ -166,4 +168,12 @@ public class SoundStreamHead2Tag extends CharacterIdTag implements SoundStreamHe public boolean getSoundType() { return streamSoundType; } + + @Override + public List getBlocks() { + List ret = new ArrayList<>(); + SoundStreamHeadTag.populateSoundStreamBlocks(swf.tags, this, ret); + return ret; + + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java index f385ac8b3..004c7a296 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java @@ -20,6 +20,8 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.Container; +import com.jpexs.decompiler.flash.tags.base.ContainerItem; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.Conditional; @@ -30,6 +32,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; /** * @@ -157,4 +161,34 @@ public class SoundStreamHeadTag extends CharacterIdTag implements SoundStreamHea public boolean getSoundType() { return streamSoundType; } + + public static void populateSoundStreamBlocks(List tags, Tag head, List output) { + boolean found = false; + for (ContainerItem t : tags) { + if (t == head) { + found = true; + continue; + } + if (!found) { + continue; + } + if (t instanceof SoundStreamBlockTag) { + output.add((SoundStreamBlockTag) t); + } + if (t instanceof SoundStreamHeadTypeTag) { + break; + } + if (t instanceof Container) { + populateSoundStreamBlocks(((Container) t).getSubItems(), head, output); + } + } + } + + @Override + public List getBlocks() { + List ret = new ArrayList<>(); + populateSoundStreamBlocks(swf.tags, this, ret); + return ret; + + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java index 12a8b66d2..20a9a3613 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java @@ -16,6 +16,9 @@ */ package com.jpexs.decompiler.flash.tags.base; +import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; +import java.util.List; + /** * * @author JPEXS @@ -37,4 +40,6 @@ public interface SoundStreamHeadTypeTag { public int getCharacterId(); public String getExportFormat(); + + public List getBlocks(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3FRAME.java b/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3FRAME.java index 129c5cc44..ac7a94c62 100644 --- a/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3FRAME.java +++ b/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3FRAME.java @@ -18,8 +18,12 @@ package com.jpexs.decompiler.flash.types.sound; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -40,6 +44,7 @@ public class MP3FRAME { public boolean copyright; public boolean original; public int emphasis; + public byte[] sampleData; public MP3FRAME(InputStream is) throws IOException { SWFInputStream sis = new SWFInputStream(is, SWF.DEFAULT_VERSION); @@ -53,7 +58,36 @@ public class MP3FRAME { reserved = (int) sis.readUB(1); channelMode = (int) sis.readUB(2); modelExtension = (int) sis.readUB(2); - //TODO:read sample data + + int size = getSampleDataSize(); + int sizeBytes = (int) Math.ceil((double) size / 8.0); + sampleData = sis.readBytes(sizeBytes); + } + + public byte[] getData() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); + sos.writeUB(11, syncWord); + sos.writeUB(1, mpegVersion); + sos.writeUB(2, layer); + sos.writeUB(1, protectionBit ? 1 : 0); + sos.writeUB(4, bitRate); + sos.writeUB(2, samplingRate); + sos.writeUB(1, paddingBit ? 1 : 0); + sos.writeUB(1, reserved); + sos.writeUB(2, channelMode); + sos.writeUB(2, modelExtension); + sos.write(sampleData); + return baos.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(MP3FRAME.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + private int getSampleDataSize() { + return (((isMPEG1() ? 144 : 72) * bitRate) / samplingRate) + (paddingBit ? 1 : 0) - 4; } public boolean isMPEG2() { diff --git a/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3SOUNDDATA.java b/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3SOUNDDATA.java new file mode 100644 index 000000000..65668a1c0 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/types/sound/MP3SOUNDDATA.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010-2014 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.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class MP3SOUNDDATA { + + public int seekSamples; + public List frames; + + public MP3SOUNDDATA(InputStream is) throws IOException { + SWFInputStream sis = new SWFInputStream(is, SWF.DEFAULT_VERSION); + seekSamples = sis.readUI16(); + frames = new ArrayList<>(); + while (sis.available() > 0) { + frames.add(new MP3FRAME(sis)); + } + } +} diff --git a/trunk/src/com/jpexs/helpers/sound/MP3Player.java b/trunk/src/com/jpexs/helpers/sound/MP3Player.java new file mode 100644 index 000000000..360d7e36c --- /dev/null +++ b/trunk/src/com/jpexs/helpers/sound/MP3Player.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010-2014 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.helpers.sound; + +/** + * + * @author JPEXS + */ +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import javazoom.jl.decoder.Bitstream; +import javazoom.jl.decoder.Decoder; +import javazoom.jl.decoder.Header; +import javazoom.jl.decoder.JavaLayerException; +import javazoom.jl.decoder.SampleBuffer; +import javazoom.jl.player.AudioDevice; +import javazoom.jl.player.FactoryRegistry; + +public class MP3Player extends SoundPlayer { + + private Bitstream bitstream; + + private Decoder decoder; + + private AudioDevice audio; + + private boolean closed = false; + + private boolean complete = false; + + private int positionSamples = 0; + private long sampleCount; + private int frameRate; + + public MP3Player(InputStream stream, long sampleCount, int frameRate) throws JavaLayerException { + this(stream, null); + this.sampleCount = sampleCount; + this.frameRate = frameRate; + } + + private MP3Player(InputStream stream, AudioDevice device) throws JavaLayerException { + super(stream); + bitstream = new Bitstream(stream); + decoder = new Decoder(); + + if (device != null) { + audio = device; + } else { + FactoryRegistry r = FactoryRegistry.systemRegistry(); + audio = r.createAudioDevice(); + } + audio.open(decoder); + } + + @Override + public void play() { + try { + play(Integer.MAX_VALUE); + } catch (JavaLayerException ex) { + Logger.getLogger(MP3Player.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private boolean play(int frames) throws JavaLayerException { + if (complete) { + return false; + } + boolean ret = true; + + while (frames-- > 0 && ret) { + ret = decodeFrame(); + } + + if (!ret) { + // last frame, ensure all data flushed to the audio device. + AudioDevice out = audio; + if (out != null) { + out.flush(); + synchronized (this) { + complete = (!closed); + stop(); + } + } + } + return ret; + } + + @Override + public synchronized void skip(long samples) { + if (complete) { + return; + } + long endPosition = positionSamples + samples; + if (samples == 0) { + return; + } + try { + while (true) { + Header h = bitstream.readFrame(); + if (h == null) { + complete = true; + return; + } + SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream); + positionSamples += output.getBufferLength(); + bitstream.closeFrame(); + if (positionSamples >= endPosition) { + break; + } + } + } catch (Exception ex) { + //ingore + } + + } + + @Override + public synchronized void stop() { + AudioDevice out = audio; + if (out != null) { + closed = true; + audio = null; + out.close(); + } + } + + @Override + public synchronized boolean isPlaying() { + return !complete; + } + + @Override + public long getSamplePosition() { + return positionSamples; + } + + private boolean decodeFrame() throws JavaLayerException { + try { + AudioDevice out = audio; + if (out == null) { + return false; + } + + Header h = bitstream.readFrame(); + if (h == null) { + return false; + } + + SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream); + positionSamples += output.getBufferLength(); + + synchronized (this) { + out = audio; + if (out != null) { + out.write(output.getBuffer(), 0, output.getBufferLength()); + } + } + + bitstream.closeFrame(); + } catch (RuntimeException ex) { + return true; + } + return true; + } + + @Override + public long samplesCount() { + return sampleCount; + } + + @Override + public long getFrameRate() { + return frameRate; + } + +} diff --git a/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java b/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java new file mode 100644 index 000000000..ee448903d --- /dev/null +++ b/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010-2014 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.helpers.sound; + +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public abstract class SoundPlayer { + + protected InputStream is; + + protected SoundPlayer(InputStream is) { + this.is = is; + } + + public abstract long samplesCount(); + + public abstract long getSamplePosition(); + + public abstract void play(); + + public abstract void stop(); + + public abstract void skip(long samples); + + public abstract boolean isPlaying(); + + public abstract long getFrameRate(); + +} diff --git a/trunk/src/com/jpexs/helpers/sound/WavPlayer.java b/trunk/src/com/jpexs/helpers/sound/WavPlayer.java new file mode 100644 index 000000000..5174373f4 --- /dev/null +++ b/trunk/src/com/jpexs/helpers/sound/WavPlayer.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2010-2014 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.helpers.sound; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * + * @author JPEXS + */ +public class WavPlayer extends SoundPlayer { + + private Clip clip; + private boolean complete = false; + private long startPos = 0; + + public WavPlayer(InputStream is) { + super(is); + try { + clip = (Clip) AudioSystem.getLine(new Line.Info(Clip.class)); + } catch (LineUnavailableException ex) { + Logger.getLogger(WavPlayer.class.getName()).log(Level.SEVERE, null, ex); + } + + if (clip == null) { + return; + } + try { + clip.open(AudioSystem.getAudioInputStream(new BufferedInputStream(is))); + } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { + Logger.getLogger(WavPlayer.class.getName()).log(Level.SEVERE, "Error opening", ex); + clip = null; + } + } + + @Override + public long samplesCount() { + return clip.getMicrosecondLength(); + } + + @Override + public void play() { + if (clip == null) { + return; + } + clip.setMicrosecondPosition(startPos);//startPos); + final WavPlayer t = this; + clip.addLineListener(new LineListener() { + + @Override + public void update(LineEvent event) { + if (event.getType() == LineEvent.Type.STOP) { + complete = true; + startPos = 0; + clip.close(); + synchronized (t) { + t.notifyAll(); + } + } + } + }); + clip.start(); + try { + synchronized (this) { + wait(); + } + } catch (InterruptedException ex) { + //Ignore + } + + } + + @Override + public long getSamplePosition() { + return clip.getMicrosecondPosition(); + } + + @Override + public void skip(long frames) { + startPos = getSamplePosition() + frames; + } + + @Override + public void stop() { + clip.stop(); + } + + @Override + public boolean isPlaying() { + return !complete; + } + + @Override + public long getFrameRate() { + return 1000000L; + } + +}