Replace SoundStreamHead (no MP3 support yet)

This commit is contained in:
Jindra Petřík
2022-12-26 18:07:45 +01:00
parent 971e2ab1fe
commit 94df71e07a
15 changed files with 505 additions and 70 deletions

View File

@@ -35,7 +35,6 @@ public class SoundStreamBlockTag extends Tag {
public static final String NAME = "SoundStreamBlock";
@HideInRawEdit
public ByteArrayRange streamSoundData;
/**

View File

@@ -40,7 +40,7 @@ import java.util.List;
* @author JPEXS
*/
@SWFVersion(from = 3)
public class SoundStreamHead2Tag extends Tag implements SoundStreamHeadTypeTag {
public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag {
public static final int ID = 45;
@@ -209,11 +209,6 @@ public class SoundStreamHead2Tag extends Tag implements SoundStreamHeadTypeTag {
return false;
}
@Override
public boolean setSound(InputStream is, int newSoundFormat) {
return false;
}
@Override
public List<ByteArrayRange> getRawSoundData() {
List<ByteArrayRange> ret = new ArrayList<>();
@@ -265,4 +260,29 @@ public class SoundStreamHead2Tag extends Tag implements SoundStreamHeadTypeTag {
}
//getNeededCharacters intentionally not defined
@Override
protected void setSoundSize(boolean soundSize) {
this.streamSoundSize = soundSize;
}
@Override
protected void setSoundType(boolean soundType) {
this.streamSoundType = soundType;
}
@Override
protected void setSoundSampleCount(int soundSampleCount) {
this.streamSoundSampleCount = soundSampleCount;
}
@Override
protected void setSoundCompression(int soundCompression) {
this.streamSoundCompression = soundCompression;
}
@Override
protected void setSoundRate(int soundRate) {
this.streamSoundRate = soundRate;
}
}

View File

@@ -41,7 +41,7 @@ import java.util.List;
* @author JPEXS
*/
@SWFVersion(from = 1)
public class SoundStreamHeadTag extends Tag implements SoundStreamHeadTypeTag {
public class SoundStreamHeadTag extends SoundStreamHeadTypeTag {
public static final int ID = 18;
@@ -217,12 +217,7 @@ public class SoundStreamHeadTag extends Tag implements SoundStreamHeadTypeTag {
@Override
public boolean importSupported() {
return false;
}
@Override
public boolean setSound(InputStream is, int newSoundFormat) {
return false;
}
}
@Override
public List<ByteArrayRange> getRawSoundData() {
@@ -275,4 +270,29 @@ public class SoundStreamHeadTag extends Tag implements SoundStreamHeadTypeTag {
}
//getNeededCharacters intentionally not defined
@Override
protected void setSoundSize(boolean soundSize) {
this.streamSoundSize = soundSize;
}
@Override
protected void setSoundType(boolean soundType) {
this.streamSoundType = soundType;
}
@Override
protected void setSoundSampleCount(int soundSampleCount) {
this.streamSoundSampleCount = soundSampleCount;
}
@Override
protected void setSoundCompression(int soundCompression) {
this.streamSoundCompression = soundCompression;
}
@Override
protected void setSoundRate(int soundRate) {
this.streamSoundRate = soundRate;
}
}

View File

@@ -16,21 +16,276 @@
*/
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.ReadOnlyTagList;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.tags.ShowFrameTag;
import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
import com.jpexs.decompiler.flash.tags.SoundStreamHeadTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.types.sound.MP3FRAME;
import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA;
import com.jpexs.decompiler.flash.types.sound.SoundFormat;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
/**
*
* @author JPEXS
*/
public interface SoundStreamHeadTypeTag extends CharacterIdTag, SoundTag {
public abstract class SoundStreamHeadTypeTag extends Tag implements CharacterIdTag, SoundTag {
public SoundStreamHeadTypeTag(SWF swf, int id, String name, ByteArrayRange data) {
super(swf, id, name, data);
}
@Override
public boolean getSoundSize();
public abstract boolean getSoundSize();
public long getSoundSampleCount();
public abstract long getSoundSampleCount();
public void setVirtualCharacterId(int ch);
public abstract void setVirtualCharacterId(int ch);
public abstract List<SoundStreamBlockTag> getBlocks();
protected abstract void setSoundSize(boolean soundSize);
protected abstract void setSoundType(boolean soundType);
protected abstract void setSoundSampleCount(int soundSampleCount);
protected abstract void setSoundCompression(int soundCompression);
protected abstract void setSoundRate(int soundRate);
@Override
public boolean setSound(InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException {
List<MP3FRAME> mp3Frames = null;
int newSoundRate = -1;
boolean newSoundSize = false;
boolean newSoundType = false;
long newSoundSampleCount = -1;
byte[] uncompressedSoundData = null;
int bytesPerSwfFrame = -1;
SWF swf = getSwf();
switch (newSoundFormat) {
case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN:
try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) {
AudioFormat fmt = audioIs.getFormat();
newSoundType = fmt.getChannels() == 2;
newSoundSize = fmt.getSampleSizeInBits() == 16;
//newSoundSampleCount = audioIs.getFrameLength();
uncompressedSoundData = Helper.readStream(audioIs);
int sampleLen = (newSoundType ? 2 : 1) * (newSoundSize ? 2 : 1);
newSoundRate = (int) Math.round(fmt.getSampleRate());
newSoundSampleCount = (int) Math.ceil(newSoundRate / swf.frameRate);
bytesPerSwfFrame = (int) Math.ceil(newSoundRate / swf.frameRate) * sampleLen;
switch (newSoundRate) {
case 5512:
newSoundRate = 0;
break;
case 11025:
newSoundRate = 1;
break;
case 22050:
newSoundRate = 2;
break;
case 44100:
newSoundRate = 3;
break;
default:
throw new UnsupportedSamplingRateException(newSoundRate, new int[]{5512, 11025, 22050, 44100});
}
} catch (UnsupportedAudioFileException | IOException ex) {
return false;
}
break;
case SoundFormat.FORMAT_MP3:
BufferedInputStream bis = new BufferedInputStream(is);
loadID3v2(bis);
byte[] mp3data = Helper.readStream(bis);
final int ID3_V1_LENTGH = 128;
final int ID3_V1_EXT_LENGTH = 227;
if (mp3data.length > ID3_V1_LENTGH) {
//ID3v1
if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') {
mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH);
if (mp3data.length > ID3_V1_EXT_LENGTH) {
//ID3v1 extended
if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') {
mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH);
}
}
}
}
try {
MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true);
if (!snd.frames.isEmpty()) {
MP3FRAME fr = snd.frames.get(0);
newSoundRate = fr.getSamplingRate();
switch (newSoundRate) {
case 11025:
newSoundRate = 1;
break;
case 22050:
newSoundRate = 2;
break;
case 44100:
newSoundRate = 3;
break;
default:
throw new UnsupportedSamplingRateException(newSoundRate, new int[]{11025, 22050, 44100});
}
newSoundSize = true;
newSoundType = fr.isStereo();
int len = snd.sampleCount();
if (fr.isStereo()) {
len = len / 2;
}
newSoundSampleCount = len;
}
mp3Frames = snd.frames;
} catch (IOException ex) {
return false;
}
break;
default:
return false;
}
ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData);
List<SoundStreamBlockTag> existingBlocks = getBlocks();
int startFrame = 0;
Timelined timelined = getTimelined();
if (!existingBlocks.isEmpty()) {
ReadOnlyTagList tags = timelined.getTags();
for (Tag t : tags) {
if (t instanceof ShowFrameTag) {
startFrame++;
}
if (t instanceof SoundStreamBlockTag) {
break;
}
}
}
for (SoundStreamBlockTag block : existingBlocks) {
timelined.removeTag(block);
}
List<SoundStreamBlockTag> blocks = new ArrayList<>();
if (bais != null) {
DataInputStream dais = new DataInputStream(bais);
try {
while (dais.available() > 0) {
byte buf[] = new byte[bytesPerSwfFrame];
dais.readFully(buf);
SoundStreamBlockTag block = new SoundStreamBlockTag(swf);
block.streamSoundData = new ByteArrayRange(buf);
blocks.add(block);
}
} catch (IOException ex) {
//ignore
}
}
ReadOnlyTagList tags = timelined.getTags();
int frame = -1;
for (int i = 0; i < tags.size(); i++) {
Tag t = tags.get(i);
if (t instanceof ShowFrameTag) {
frame++;
if (frame >= startFrame && !blocks.isEmpty()) {
SoundStreamBlockTag block = blocks.remove(0);
timelined.addTag(i, block);
tags = timelined.getTags();
i++;
}
}
}
setSoundCompression(newSoundFormat);
setSoundSampleCount((int)newSoundSampleCount);
setSoundSize(newSoundSize);
setSoundType(newSoundType);
setSoundRate(newSoundRate);
setModified(true);
timelined.resetTimeline();
swf.resetTimeline(); //to reload blocks
return true;
}
private void loadID3v2(InputStream in) {
int size = -1;
try {
// Read ID3v2 header (10 bytes).
in.mark(10);
size = readID3v2Header(in);
} catch (IOException e) {
} finally {
try {
// Unread ID3v2 header (10 bytes).
in.reset();
} catch (IOException e) {
}
}
// Load ID3v2 tags.
try {
if (size > 0) {
byte[] rawid3v2 = new byte[size];
in.read(rawid3v2, 0, rawid3v2.length);
}
} catch (IOException e) {
}
}
/**
* Parse ID3v2 tag header to find out size of ID3v2 frames.
*
* @param in MP3 InputStream
* @return size of ID3v2 frames + header
* @throws IOException
* @author JavaZOOM
*/
private int readID3v2Header(InputStream in) throws IOException {
byte[] id3header = new byte[4];
int size = -10;
in.read(id3header, 0, 3);
// Look for ID3v2
if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) {
in.read(id3header, 0, 3);
int majorVersion = id3header[0];
int revision = id3header[1];
in.read(id3header, 0, 4);
size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]);
}
return (size + 10);
}
public List<SoundStreamBlockTag> getBlocks();
}

View File

@@ -1766,9 +1766,10 @@ public class XFLConverter {
for (int ch : characters.keySet()) {
CharacterTag symbol = characters.get(ch);
if (symbol instanceof ImageTag
|| symbol instanceof SoundStreamHeadTypeTag || symbol instanceof DefineSoundTag
|| symbol instanceof DefineSoundTag
|| symbol instanceof DefineVideoStreamTag) {
hasMedia = true;
//symbol instanceof SoundStreamHeadTypeTag FIXME
hasMedia = true;
}
}
@@ -1854,7 +1855,7 @@ public class XFLConverter {
writer.writeAttribute("frameBottom", image.getHeight());
writer.writeEndElement();
mediaCount++;
} else if ((symbol instanceof SoundStreamHeadTypeTag) || (symbol instanceof DefineSoundTag)) {
} else if (/*(symbol instanceof SoundStreamHeadTypeTag) || FIXME */(symbol instanceof DefineSoundTag)) {
int soundFormat = 0;
int soundRate = 0;
boolean soundType = false;
@@ -1863,8 +1864,8 @@ public class XFLConverter {
byte[] soundData = SWFInputStream.BYTE_ARRAY_EMPTY;
int[] rateMap = {5, 11, 22, 44};
String exportFormat = "flv";
if (symbol instanceof SoundStreamHeadTypeTag) {
SoundStreamHeadTypeTag sstream = (SoundStreamHeadTypeTag) symbol;
if (false) { //FIXME symbol instanceof SoundStreamHeadTypeTag) {
SoundStreamHeadTypeTag sstream = null; //(SoundStreamHeadTypeTag) symbol;
soundFormat = sstream.getSoundFormatId();
soundRate = sstream.getSoundRate();
soundType = sstream.getSoundType();