Fixed: Flash viewer - Sound envelope handling

This commit is contained in:
Jindra Petřík
2021-03-23 22:41:19 +01:00
parent efdcb27963
commit 9a45708ebb
17 changed files with 313 additions and 52 deletions

View File

@@ -144,8 +144,11 @@ import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.SOUNDENVELOPE;
import com.jpexs.decompiler.flash.types.SOUNDINFO;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.SWFField;
import com.jpexs.decompiler.flash.types.sound.SoundInfoSoundCacheEntry;
import com.jpexs.decompiler.flash.xfl.FLAVersion;
import com.jpexs.decompiler.flash.xfl.XFLConverter;
import com.jpexs.decompiler.flash.xfl.XFLExportSettings;
@@ -341,7 +344,7 @@ public final class SWF implements SWFContainerItem, Timelined {
private final Cache<SHAPE, ShapeExportData> shapeExportDataCache = Cache.getInstance(true, true, "shapeExportData");
@Internal
private final Cache<SoundTag, byte[]> soundCache = Cache.getInstance(false, false, "sound");
private final Cache<SoundInfoSoundCacheEntry, byte[]> soundCache = Cache.getInstance(false, false, "sound");
@Internal
public final AS2Cache as2Cache = new AS2Cache();
@@ -2577,9 +2580,10 @@ public final class SWF implements SWFContainerItem, Timelined {
return null;
}
public byte[] getFromCache(SoundTag soundTag) {
if (soundCache.contains(soundTag)) {
return soundCache.get(soundTag);
public byte[] getFromCache(SOUNDINFO soundInfo, SoundTag soundTag) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag);
if (soundCache.contains(key)) {
return soundCache.get(key);
}
return null;
}
@@ -2590,8 +2594,9 @@ public final class SWF implements SWFContainerItem, Timelined {
}
}
public void putToCache(SoundTag soundTag, byte[] data) {
soundCache.put(soundTag, data);
public void putToCache(SOUNDINFO soundInfo, SoundTag soundTag, byte[] data) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag);
soundCache.put(key, data);
}
public void clearImageCache() {

View File

@@ -12,7 +12,8 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* License along with this library.
*/
package com.jpexs.decompiler.flash.exporters;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
@@ -156,7 +157,7 @@ public class SoundExporter {
}
} else {
List<ByteArrayRange> soundData = st.getRawSoundData();
List<ByteArrayRange> soundData = st.getRawSoundData();
fmt.createWav(null, soundData, fos);
}
}
}

View File

@@ -12,7 +12,8 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* License along with this library.
*/
package com.jpexs.decompiler.flash.types;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
@@ -32,4 +33,38 @@ public class SOUNDENVELOPE implements Serializable {
@SWFType(BasicType.UI16)
public int rightLevel;
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + (int) (this.pos44 ^ (this.pos44 >>> 32));
hash = 29 * hash + this.leftLevel;
hash = 29 * hash + this.rightLevel;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SOUNDENVELOPE other = (SOUNDENVELOPE) obj;
if (this.pos44 != other.pos44) {
return false;
}
if (this.leftLevel != other.leftLevel) {
return false;
}
if (this.rightLevel != other.rightLevel) {
return false;
}
return true;
}
}

View File

@@ -12,13 +12,15 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* License along with this library.
*/
package com.jpexs.decompiler.flash.types;
import com.jpexs.decompiler.flash.types.annotations.Conditional;
import com.jpexs.decompiler.flash.types.annotations.Reserved;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import java.io.Serializable;
import java.util.Arrays;
/**
*
@@ -56,4 +58,70 @@ public class SOUNDINFO implements Serializable {
@Conditional("hasEnvelope")
public SOUNDENVELOPE[] envelopeRecords = new SOUNDENVELOPE[0];
@Override
public int hashCode() {
int hash = 3;
hash = 17 * hash + this.reserved;
hash = 17 * hash + (this.syncStop ? 1 : 0);
hash = 17 * hash + (this.syncNoMultiple ? 1 : 0);
hash = 17 * hash + (this.hasEnvelope ? 1 : 0);
hash = 17 * hash + (this.hasLoops ? 1 : 0);
hash = 17 * hash + (this.hasOutPoint ? 1 : 0);
hash = 17 * hash + (this.hasInPoint ? 1 : 0);
hash = 17 * hash + (int) (this.inPoint ^ (this.inPoint >>> 32));
hash = 17 * hash + (int) (this.outPoint ^ (this.outPoint >>> 32));
hash = 17 * hash + this.loopCount;
hash = 17 * hash + Arrays.deepHashCode(this.envelopeRecords);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SOUNDINFO other = (SOUNDINFO) obj;
if (this.reserved != other.reserved) {
return false;
}
if (this.syncStop != other.syncStop) {
return false;
}
if (this.syncNoMultiple != other.syncNoMultiple) {
return false;
}
if (this.hasEnvelope != other.hasEnvelope) {
return false;
}
if (this.hasLoops != other.hasLoops) {
return false;
}
if (this.hasOutPoint != other.hasOutPoint) {
return false;
}
if (this.hasInPoint != other.hasInPoint) {
return false;
}
if (this.inPoint != other.inPoint) {
return false;
}
if (this.outPoint != other.outPoint) {
return false;
}
if (this.loopCount != other.loopCount) {
return false;
}
if (!Arrays.deepEquals(this.envelopeRecords, other.envelopeRecords)) {
return false;
}
return true;
}
}

View File

@@ -12,10 +12,12 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* License along with this library.
*/
package com.jpexs.decompiler.flash.types.sound;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.types.SOUNDINFO;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.io.ByteArrayOutputStream;
@@ -198,7 +200,7 @@ public class SoundFormat {
}
}
public boolean createWav(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, OutputStream os) throws IOException {
ensureFormat();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SoundDecoder decoder = getDecoder();
@@ -208,8 +210,56 @@ public class SoundFormat {
decoder.decode(sis, baos);
}
/*
System.err.println("sampling rate:" + samplingRate);
System.err.println("len:" + baos.toByteArray().length);
*/
boolean convertedStereo = stereo;
ByteArrayOutputStream baosFiltered;
if (soundInfo == null) {
baosFiltered = baos;
} 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;
for (int i = inPointBytes; i < outPointBytes; i += (stereo ? 4 : 2)) {
int left = ((data[i] & 0xff) + ((data[i + 1] & 0xff) << 8)) << 16 >> 16;
int right = left;
if (stereo) {
right = ((data[i + 2] & 0xff) + ((data[i + 3] & 0xff) << 8)) << 16 >> 16;
}
if (soundInfo.hasEnvelope) {
for (int e = 0; e < soundInfo.envelopeRecords.length - 1; e++) {
int envPosBytes = inPointBytes + (int) (soundInfo.envelopeRecords[e].pos44 * samplingRate / 44100.0 * 2 * (stereo ? 2 : 1));
int envNextPosBytes = inPointBytes + (int) (soundInfo.envelopeRecords[e + 1].pos44 * samplingRate / 44100.0 * 2 * (stereo ? 2 : 1));
if (i >= envPosBytes && i <= envNextPosBytes) {
double pos = (i - envPosBytes) / (double) (envNextPosBytes - envPosBytes);
int leftLevel = (int) (soundInfo.envelopeRecords[e].leftLevel + (soundInfo.envelopeRecords[e + 1].leftLevel - soundInfo.envelopeRecords[e].leftLevel) * pos);
int rightLevel = (int) (soundInfo.envelopeRecords[e].rightLevel + (soundInfo.envelopeRecords[e + 1].rightLevel - soundInfo.envelopeRecords[e].rightLevel) * pos);
double leftMultiplier = leftLevel / 32768.0;
double rightMultiplier = rightLevel / 32768.0;
left = (int) Math.round(left * leftMultiplier);
right = (int) Math.round(right * rightMultiplier);
break;
}
}
}
writeLE(baosFiltered, left, 2);
writeLE(baosFiltered, right, 2);
}
convertedStereo = true;
}
try {
try {
createWavFromPcmData(os, samplingRate, true, convertedStereo, baosFiltered.toByteArray());
return true;
} catch (IOException ex) {
return false;

View File

@@ -0,0 +1,49 @@
package com.jpexs.decompiler.flash.types.sound;
import com.jpexs.decompiler.flash.tags.base.SoundTag;
import com.jpexs.decompiler.flash.types.SOUNDINFO;
import java.util.Objects;
/**
*
* @author JPEXS
*/
public class SoundInfoSoundCacheEntry {
public SOUNDINFO soundInfo;
public SoundTag soundTag;
public SoundInfoSoundCacheEntry(SOUNDINFO soundInfo, SoundTag soundTag) {
this.soundInfo = soundInfo;
this.soundTag = soundTag;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + Objects.hashCode(this.soundInfo);
hash = 47 * hash + Objects.hashCode(this.soundTag);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SoundInfoSoundCacheEntry other = (SoundInfoSoundCacheEntry) obj;
if (!Objects.equals(this.soundInfo, other.soundInfo)) {
return false;
}
if (!Objects.equals(this.soundTag, other.soundTag)) {
return false;
}
return true;
}
}