Fixed #2143 FLA Export / Sound playback - taking MP3 initial latency into account

This commit is contained in:
Jindra Petřík
2023-12-13 20:05:10 +01:00
parent 779be797e2
commit 2404151922
14 changed files with 130 additions and 44 deletions

View File

@@ -127,7 +127,7 @@ public class CallMethodActionItem extends ActionItem {
scriptObject.toString(writer, localData);
}
if (
!(((DirectValueActionItem)methodName).value instanceof RegisterNumber)
!(((DirectValueActionItem) methodName).value instanceof RegisterNumber)
&& IdentifiersDeobfuscation.isValidName(false, methodName.toStringNoQuotes(localData))
) {
writer.append(".");

View File

@@ -194,7 +194,7 @@ public class SoundExporter {
}
} else {
List<ByteArrayRange> soundData = st.getRawSoundData();
fmt.createWav(null, soundData, fos);
fmt.createWav(null, soundData, fos, st.getInitialLatency());
}
}
}

View File

@@ -31,6 +31,8 @@ import com.jpexs.helpers.ByteArrayRange;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
@@ -137,6 +139,9 @@ public class DefineSoundTag extends CharacterTag implements SoundTag {
@Override
public SoundExportFormat getExportFormat() {
if (soundFormat == SoundFormat.FORMAT_MP3) {
if (getInitialLatency() > 0) {
return SoundExportFormat.WAV;
}
return SoundExportFormat.MP3;
}
if (soundFormat == SoundFormat.FORMAT_ADPCM) {
@@ -241,5 +246,19 @@ public class DefineSoundTag extends CharacterTag implements SoundTag {
@Override
public String getFlaExportName() {
return "sound" + getCharacterId();
}
@Override
public int getInitialLatency() {
if (soundFormat == SoundFormat.FORMAT_MP3) {
SWFInputStream sis;
try {
sis = new SWFInputStream(null, soundData.getRangeData(0, 2));
return sis.readSI16("seekSamples");
} catch (IOException ex) {
//ignore
}
}
return 0;
}
}

View File

@@ -290,4 +290,9 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag {
public String getFlaExportName() {
return "sound" + getCharacterId();
}
@Override
public int getInitialLatency() {
return 0;
}
}

View File

@@ -299,4 +299,9 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag {
public String getFlaExportName() {
return "sound" + getCharacterId();
}
@Override
public int getInitialLatency() {
return 0;
}
}

View File

@@ -65,4 +65,6 @@ public interface SoundTag extends TreeItem {
public String getName();
public String getFlaExportName();
public int getInitialLatency();
}

View File

@@ -247,5 +247,10 @@ public class DefineExternalSound extends CharacterTag implements SoundTag {
@Override
public String getFlaExportName() {
return "sound" + getCharacterId();
}
@Override
public int getInitialLatency() {
return 0;
}
}

View File

@@ -260,4 +260,9 @@ public class DefineExternalStreamSound extends Tag implements CharacterIdTag, So
public String getFlaExportName() {
return "sound" + getCharacterId();
}
@Override
public int getInitialLatency() {
return 0;
}
}

View File

@@ -164,4 +164,9 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag {
public String getFlaExportName() {
return head.getFlaExportName() + "_" + (startFrame + 1) + "-" + (endFrame + 1);
}
@Override
public int getInitialLatency() {
return 0;
}
}

View File

@@ -23,6 +23,7 @@ import com.jpexs.helpers.utf8.Utf8Helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
@@ -86,28 +87,11 @@ public class SoundFormat {
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)) {
if (!SoundFormat.this.decode(sis, baos)) {
return false;
}
@@ -200,7 +184,24 @@ public class SoundFormat {
}
}
public boolean createWav(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, OutputStream os) throws IOException {
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 byte[] decode(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, int skipSamples) throws IOException {
ensureFormat();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SoundDecoder decoder = getDecoder();
@@ -209,34 +210,48 @@ public class SoundFormat {
sis.seek(dataRange.getPos());
decoder.decode(sis, baos);
}
/*
System.err.println("sampling rate:" + samplingRate);
System.err.println("len:" + baos.toByteArray().length);
*/
byte[] decodedData = baos.toByteArray();
if (skipSamples > 0) {
byte[] data = decodedData;
data = Arrays.copyOfRange(
data,
skipSamples * 2 * (stereo ? 2 : 1),
data.length
);
return data;
}
return decodedData;
}
public boolean createWav(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, OutputStream os, int skipSamples) throws IOException {
byte[] decodedData = decode(soundInfo, dataRanges, skipSamples);
boolean convertedStereo = stereo;
ByteArrayOutputStream baosFiltered;
if (soundInfo == null) {
baosFiltered = baos;
baosFiltered = new ByteArrayOutputStream();
baosFiltered.write(decodedData);
} else {
int inPoint = (soundInfo.hasInPoint ? (int) Math.round(soundInfo.inPoint * samplingRate / 44100.0) : 0);
int outPoint = (soundInfo.hasOutPoint ? (int) Math.round(soundInfo.outPoint * samplingRate / 44100.0) : Integer.MAX_VALUE);
byte[] data = baos.toByteArray();
baosFiltered = new ByteArrayOutputStream();
int inPointBytes = inPoint * 2 /*16bit*/ * (stereo ? 2 : 1);
int outPointBytes = soundInfo.hasOutPoint ? outPoint * 2 /*16bit*/ * (stereo ? 2 : 1) : data.length;
//Q: Use skipSamples value?
int outPointBytes = soundInfo.hasOutPoint ? outPoint * 2 /*16bit*/ * (stereo ? 2 : 1) : decodedData.length;
for (int i = inPointBytes; i < outPointBytes; i += (stereo ? 4 : 2)) {
if (i + 1 >= data.length) {
if (i + 1 >= decodedData.length) {
break;
}
int left = ((data[i] & 0xff) + ((data[i + 1] & 0xff) << 8)) << 16 >> 16;
int left = ((decodedData[i] & 0xff) + ((decodedData[i + 1] & 0xff) << 8)) << 16 >> 16;
int right = left;
if (stereo) {
if (i + 3 >= data.length) {
if (i + 3 >= decodedData.length) {
break;
}
right = ((data[i + 2] & 0xff) + ((data[i + 3] & 0xff) << 8)) << 16 >> 16;
right = ((decodedData[i + 2] & 0xff) + ((decodedData[i + 3] & 0xff) << 8)) << 16 >> 16;
}
if (soundInfo.hasEnvelope) {

View File

@@ -162,6 +162,7 @@ import java.awt.Font;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -1961,7 +1962,7 @@ public class XFLConverter {
writer.writeEndElement();
}
private void convertSoundMedia(SWF swf, ReadOnlyTagList tags, SoundTag symbol, XFLXmlWriter writer, HashMap<String, byte[]> files) throws XMLStreamException {
private void convertSoundMedia(SWF swf, ReadOnlyTagList tags, SoundTag symbol, XFLXmlWriter writer, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles) throws XMLStreamException {
int soundFormat = 0;
int soundRate = 0;
boolean soundType = false;
@@ -2034,6 +2035,7 @@ public class XFLConverter {
logger.log(Level.SEVERE, null, ex);
}
}
int seekSamples = 0;
if (soundFormat == SoundFormat.FORMAT_MP3) {
exportFormat = "mp3";
if (!soundType) { //mono
@@ -2043,9 +2045,13 @@ public class XFLConverter {
try {
SWFInputStream sis = new SWFInputStream(swf, soundData);
MP3SOUNDDATA s = new MP3SOUNDDATA(sis, false);
if (s.seekSamples > 0) {
seekSamples = s.seekSamples;
exportFormat = "wav";
}
if (!s.frames.isEmpty()) {
MP3FRAME frame = s.frames.get(0);
int bitRate = frame.getBitRate();
int bitRate = frame.getBitRate() / 1000;
switch (bitRate) {
case 8:
@@ -2084,8 +2090,7 @@ public class XFLConverter {
case 160:
bits = 17;
break;
}
}
}
} catch (IOException | IndexOutOfBoundsException ex) {
logger.log(Level.SEVERE, null, ex);
@@ -2095,10 +2100,22 @@ public class XFLConverter {
SoundFormat fmt = st.getSoundFormat();
byte[] data = SWFInputStream.BYTE_ARRAY_EMPTY;
try {
data = new SoundExporter().exportSound(st, SoundExportMode.MP3_WAV);
data = new SoundExporter().exportSound(st, seekSamples > 0 ? SoundExportMode.WAV : SoundExportMode.MP3_WAV);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
String datFileName = null;
if (seekSamples > 0) {
long ts = getTimestamp(swf);
datFileName = "M " + (datfiles.size() + 1) + " " + ts + ".dat";
try {
byte[] decodedData = st.getSoundFormat().decode(null, st.getRawSoundData(), seekSamples);
datfiles.put(datFileName, decodedData);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
String symbolFile = symbol.getFlaExportName() + "." + exportFormat;
files.put(symbolFile, data);
@@ -2107,6 +2124,9 @@ public class XFLConverter {
"sourceLastImported", Long.toString(getTimestamp(swf)),
"externalFileSize", Integer.toString(data.length)});
writer.writeAttribute("href", symbolFile);
if (datFileName != null) {
writer.writeAttribute("soundDataHRef", datFileName);
}
writer.writeAttribute("format", rateMap[soundRate] + "kHz" + " " + (soundSize ? "16bit" : "8bit") + " " + (soundType ? "Stereo" : "Mono"));
writer.writeAttribute("exportFormat", format);
writer.writeAttribute("exportBits", bits);
@@ -2227,7 +2247,7 @@ public class XFLConverter {
statusStack.popStatus();
} else if (symbol instanceof DefineSoundTag) {
statusStack.pushStatus(symbol.toString());
convertSoundMedia(swf, tags, (DefineSoundTag) symbol, writer, files);
convertSoundMedia(swf, tags, (DefineSoundTag) symbol, writer, files, datfiles);
boolean linkageExportForAS = false;
if (characterClasses.containsKey(symbol.getCharacterId())) {
@@ -2336,7 +2356,7 @@ public class XFLConverter {
SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t;
for (SoundStreamFrameRange range : head.getRanges()) {
statusStack.pushStatus(range.toString());
convertSoundMedia(swf, tags, range, writer, files);
convertSoundMedia(swf, tags, range, writer, files, datfiles);
writer.writeEndElement();
mediaCount++;
statusStack.popStatus();
@@ -2349,7 +2369,7 @@ public class XFLConverter {
SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) st;
for (SoundStreamFrameRange range : head.getRanges()) {
statusStack.pushStatus(range.toString());
convertSoundMedia(swf, sprite.getTags(), range, writer, files);
convertSoundMedia(swf, sprite.getTags(), range, writer, files, datfiles);
writer.writeEndElement();
mediaCount++;
statusStack.popStatus();
@@ -3195,7 +3215,7 @@ public class XFLConverter {
SWF swf = startSound.getSwf();
DefineSoundTag s = swf.getSound(startSound.soundId);
if (s == null) {
logger.log(Level.WARNING, "Sount tag (ID={0}) was not found", startSound.soundId);
logger.log(Level.WARNING, "Sound tag (ID={0}) was not found", startSound.soundId);
continue;
}