mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-07 20:15:04 +00:00
#2451 Importing sound ranges of different format than head by converting to uncompressed wav
This commit is contained in:
@@ -27,7 +27,6 @@ import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.SoundImportException;
|
||||
import com.jpexs.decompiler.flash.tags.base.SoundParametersMismatchException;
|
||||
import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.SoundTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.UnsupportedSamplingRateException;
|
||||
@@ -245,7 +244,7 @@ public class SoundImporter {
|
||||
* @return True if sound stream was imported successfully
|
||||
* @throws UnsupportedSamplingRateException On unsupported sampling rate
|
||||
*/
|
||||
public boolean importSoundStream(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException, SoundParametersMismatchException {
|
||||
public boolean importSoundStream(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException {
|
||||
return importSoundStreamAtFrame(streamHead, is, newSoundFormat, null);
|
||||
}
|
||||
|
||||
@@ -260,30 +259,50 @@ public class SoundImporter {
|
||||
* @return True if sound stream was imported successfully
|
||||
* @throws UnsupportedSamplingRateException On unsupported sampling rate
|
||||
*/
|
||||
public boolean importSoundStreamAtFrame(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat, Integer startFrame) throws UnsupportedSamplingRateException, SoundParametersMismatchException {
|
||||
public boolean importSoundStreamAtFrame(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat, Integer startFrame) throws UnsupportedSamplingRateException {
|
||||
List<MP3FRAME> mp3Frames = null;
|
||||
int newSoundRate = -1;
|
||||
boolean newSoundSize = false;
|
||||
boolean newSoundType = false;
|
||||
long newSoundSampleCount = -1;
|
||||
byte[] uncompressedSoundData = null;
|
||||
int bytesPerSwfFrame = -1;
|
||||
byte[] mp3data = null;
|
||||
SWF swf = streamHead.getSwf();
|
||||
int sampleLen = 0;
|
||||
int soundRateHz = 0;
|
||||
|
||||
int bitRateOriginal = -1;
|
||||
int newBitRate = -1;
|
||||
if (streamHead.getSoundFormatId() == SoundFormat.FORMAT_MP3) {
|
||||
List<SoundStreamFrameRange> ranges = streamHead.getRanges();
|
||||
if (!ranges.isEmpty()) {
|
||||
SWFInputStream sis;
|
||||
try {
|
||||
sis = new SWFInputStream(swf, ranges.get(0).blocks.get(0).streamSoundData.getRangeData());
|
||||
MP3SOUNDDATA s = new MP3SOUNDDATA(sis, false);
|
||||
if (!s.frames.isEmpty()) {
|
||||
bitRateOriginal = s.frames.get(0).getBitRate();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
bitRateOriginal = -1;
|
||||
//newSoundSampleCount = audioIs.getFrameLength();
|
||||
uncompressedSoundData = Helper.readStream(audioIs);
|
||||
sampleLen = (newSoundType ? 2 : 1) * (newSoundSize ? 2 : 1);
|
||||
soundRateHz = (int) Math.round(fmt.getSampleRate());
|
||||
newSoundSampleCount = (int) Math.ceil(soundRateHz / swf.frameRate);
|
||||
|
||||
bytesPerSwfFrame = (int) Math.ceil(soundRateHz / swf.frameRate) * sampleLen;
|
||||
//bytesPerSwfFrame = (int) Math.ceil(soundRateHz / swf.frameRate) * sampleLen;
|
||||
switch (soundRateHz) {
|
||||
case 5512:
|
||||
newSoundRate = 0;
|
||||
@@ -308,7 +327,7 @@ public class SoundImporter {
|
||||
case SoundFormat.FORMAT_MP3:
|
||||
BufferedInputStream bis = new BufferedInputStream(is);
|
||||
loadID3v2(bis);
|
||||
byte[] mp3data = Helper.readStream(bis);
|
||||
mp3data = Helper.readStream(bis);
|
||||
|
||||
final int ID3_V1_LENTGH = 128;
|
||||
final int ID3_V1_EXT_LENGTH = 227;
|
||||
@@ -330,6 +349,7 @@ public class SoundImporter {
|
||||
if (!snd.frames.isEmpty()) {
|
||||
MP3FRAME fr = snd.frames.get(0);
|
||||
soundRateHz = fr.getSamplingRate();
|
||||
newBitRate = fr.getBitRate();
|
||||
switch (soundRateHz) {
|
||||
case 11025:
|
||||
newSoundRate = 1;
|
||||
@@ -368,20 +388,85 @@ public class SoundImporter {
|
||||
if (newSoundFormat != streamHead.getSoundFormatId()
|
||||
|| newSoundSize != streamHead.getSoundSize()
|
||||
|| newSoundType != streamHead.getSoundType()
|
||||
|| newSoundRate != streamHead.getSoundRate()) {
|
||||
throw new SoundParametersMismatchException(
|
||||
streamHead.getSoundType(),
|
||||
streamHead.getSoundSize(),
|
||||
streamHead.getSoundRate(),
|
||||
streamHead.getSoundFormatId(),
|
||||
newSoundType,
|
||||
newSoundSize,
|
||||
newSoundRate,
|
||||
newSoundFormat
|
||||
);
|
||||
|| newSoundRate != streamHead.getSoundRate()
|
||||
|| newBitRate != bitRateOriginal) {
|
||||
List<ByteArrayRange> data = streamHead.getRawSoundData();
|
||||
byte[] wholeStreamUncompressedData;
|
||||
try {
|
||||
wholeStreamUncompressedData = streamHead.getSoundFormat().decode(null, data, 0);
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
if (mp3data != null) {
|
||||
final int[] rateMap = {5512, 11025, 22050, 44100};
|
||||
SoundFormat mp3SoundFormat = new SoundFormat(SoundFormat.FORMAT_MP3, rateMap[newSoundRate], newSoundType);
|
||||
try {
|
||||
uncompressedSoundData = mp3SoundFormat.decode(null, Arrays.asList(new ByteArrayRange(mp3data)), 0);
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
mp3Frames = null;
|
||||
}
|
||||
|
||||
newSoundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN;
|
||||
|
||||
//16bit<>8bit does not match, convert to 16bit
|
||||
if (newSoundSize && !streamHead.getSoundSize()) {
|
||||
wholeStreamUncompressedData = to16bit(wholeStreamUncompressedData);
|
||||
} else if (streamHead.getSoundSize() && !newSoundSize) {
|
||||
uncompressedSoundData = to16bit(uncompressedSoundData);;
|
||||
newSoundSize = true;
|
||||
}
|
||||
|
||||
//stereo<>mono does not match, convert to stereo
|
||||
if (newSoundType && !streamHead.getSoundType()) {
|
||||
wholeStreamUncompressedData = toStereo(wholeStreamUncompressedData, newSoundSize);
|
||||
} else if (streamHead.getSoundType() && !newSoundType) {
|
||||
uncompressedSoundData = toStereo(uncompressedSoundData, newSoundSize);
|
||||
newSoundType = true;
|
||||
}
|
||||
|
||||
//sound rate does not match, convert to the higher one
|
||||
if (newSoundRate > streamHead.getSoundRate()) {
|
||||
for (int i = streamHead.getSoundRate(); i < newSoundRate; i++) {
|
||||
wholeStreamUncompressedData = toHigherRate(wholeStreamUncompressedData, newSoundSize, newSoundType);
|
||||
}
|
||||
} else if (streamHead.getSoundRate() > newSoundRate) {
|
||||
for (int i = newSoundRate; i < streamHead.getSoundRate(); i++) {
|
||||
uncompressedSoundData = toHigherRate(uncompressedSoundData, newSoundSize, newSoundType);
|
||||
}
|
||||
newSoundRate = streamHead.getSoundRate();
|
||||
}
|
||||
|
||||
sampleLen = (newSoundType ? 2 : 1) * (newSoundSize ? 2 : 1);
|
||||
final int[] rateMap = {5512, 11025, 22050, 44100};
|
||||
addStream(streamHead, wholeStreamUncompressedData, swf, rateMap[newSoundRate], sampleLen, null, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
addStream(streamHead, uncompressedSoundData, swf, soundRateHz, sampleLen, mp3Frames, startFrame, false);
|
||||
streamHead.setSoundCompression(newSoundFormat);
|
||||
streamHead.setSoundSampleCount((int) newSoundSampleCount);
|
||||
streamHead.setSoundSize(newSoundSize);
|
||||
streamHead.setSoundType(newSoundType);
|
||||
streamHead.setSoundRate(newSoundRate);
|
||||
|
||||
streamHead.setModified(true);
|
||||
streamHead.getTimelined().resetTimeline();
|
||||
swf.resetTimeline(); //to reload blocks
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addStream(
|
||||
SoundStreamHeadTypeTag streamHead,
|
||||
byte[] uncompressedSoundData,
|
||||
SWF swf,
|
||||
int soundRateHz,
|
||||
int sampleLen,
|
||||
List<MP3FRAME> mp3Frames,
|
||||
Integer startFrame,
|
||||
boolean matchRanges
|
||||
) {
|
||||
ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData);
|
||||
|
||||
List<SoundStreamFrameRange> ranges = streamHead.getRanges();
|
||||
@@ -418,7 +503,6 @@ public class SoundImporter {
|
||||
for (SoundStreamBlockTag block : existingBlocks) {
|
||||
timelined.removeTag(block);
|
||||
}
|
||||
|
||||
List<SoundStreamBlockTag> blocks = new ArrayList<>();
|
||||
if (bais != null) { //Uncompressed
|
||||
DataInputStream dais = new DataInputStream(bais);
|
||||
@@ -512,7 +596,18 @@ public class SoundImporter {
|
||||
}
|
||||
if (t instanceof ShowFrameTag) {
|
||||
frame++;
|
||||
if (frame >= startFrame && !blocks.isEmpty()) {
|
||||
boolean match = false;
|
||||
if (matchRanges) {
|
||||
for (SoundStreamFrameRange range : ranges) {
|
||||
if (frame >= range.startFrame && frame <= range.endFrame) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (frame >= startFrame) {
|
||||
match = true;
|
||||
}
|
||||
if (match && !blocks.isEmpty()) {
|
||||
SoundStreamBlockTag block = blocks.remove(0);
|
||||
block.setTimelined(timelined);
|
||||
timelined.addTag(i, block);
|
||||
@@ -534,16 +629,107 @@ public class SoundImporter {
|
||||
framesBefore++;
|
||||
}
|
||||
timelined.setFrameCount(framesBefore);
|
||||
streamHead.setSoundCompression(newSoundFormat);
|
||||
streamHead.setSoundSampleCount((int) newSoundSampleCount);
|
||||
streamHead.setSoundSize(newSoundSize);
|
||||
streamHead.setSoundType(newSoundType);
|
||||
streamHead.setSoundRate(newSoundRate);
|
||||
|
||||
streamHead.setModified(true);
|
||||
timelined.resetTimeline();
|
||||
swf.resetTimeline(); //to reload blocks
|
||||
return true;
|
||||
}
|
||||
|
||||
private byte[] toStereo(byte[] data, boolean soundSize) {
|
||||
byte[] ret = new byte[data.length * 2];
|
||||
for (int i = 0; i < data.length; i += (soundSize ? 2 : 1)) {
|
||||
if (soundSize) {
|
||||
ret[i * 2] = data[i];
|
||||
ret[i * 2 + 1] = data[i + 1];
|
||||
ret[i * 2 + 2] = data[i];
|
||||
ret[i * 2 + 3] = data[i + 1];
|
||||
} else {
|
||||
ret[i * 2] = data[i];
|
||||
ret[i * 2 + 1] = data[i];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private byte[] to16bit(byte[] data) {
|
||||
byte[] ret = new byte[data.length * 2];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int val = data[i] & 0xFF;
|
||||
val = val * 65535 / 255;
|
||||
ret[i * 2] = (byte) (val & 0xFF);
|
||||
ret[i * 2 + 1] = (byte) ((val >> 8) & 0xFF);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples sound data to higher sound rate (doubles sound rate) 5512,
|
||||
* 11025, 22050, 44100
|
||||
*
|
||||
* @param data Input data
|
||||
* @param soundSize True = 2 bytes little endian per channel, False = 1 byte
|
||||
* per channel
|
||||
* @param soundType True = Stereo = two channels, False = mono = single
|
||||
* channel
|
||||
* @return Resampled data
|
||||
*/
|
||||
private byte[] toHigherRate(byte[] data, boolean soundSize, boolean soundType) {
|
||||
int sampleLen = (soundType ? 2 : 1) * (soundSize ? 2 : 1);
|
||||
byte[] ret = new byte[data.length * 2 - sampleLen];
|
||||
int prevLeft = 0;
|
||||
int prevRight = 0;
|
||||
int retPos = 0;
|
||||
for (int i = 0; i < data.length; i += sampleLen) {
|
||||
int left;
|
||||
int right;
|
||||
if (soundSize) {
|
||||
left = (short) ((data[i] & 0xFF) + ((data[i + 1] & 0xFF) << 8));
|
||||
if (soundType) {
|
||||
right = (short) ((data[i + 2] & 0xFF) + ((data[i + 3] & 0xFF) << 8));
|
||||
} else {
|
||||
right = left;
|
||||
}
|
||||
} else {
|
||||
left = data[i];
|
||||
if (soundType) {
|
||||
right = data[i + 1];
|
||||
} else {
|
||||
right = left;
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
int midLeft = (prevLeft + left) / 2;
|
||||
int midRight = (prevRight + right) / 2;
|
||||
if (soundSize) {
|
||||
ret[retPos] = (byte) (midLeft & 0xFF);
|
||||
ret[retPos + 1] = (byte) ((midLeft >> 8) & 0xFF);
|
||||
if (soundType) {
|
||||
ret[retPos + 2] = (byte) (midRight & 0xFF);
|
||||
ret[retPos + 3] = (byte) ((midRight >> 8) & 0xFF);
|
||||
}
|
||||
} else {
|
||||
ret[retPos] = (byte) (midLeft & 0xFF);
|
||||
if (soundType) {
|
||||
ret[retPos + 1] = (byte) (midRight & 0xFF);
|
||||
}
|
||||
}
|
||||
retPos += sampleLen;
|
||||
}
|
||||
if (soundSize) {
|
||||
ret[retPos] = (byte) (left & 0xFF);
|
||||
ret[retPos + 1] = (byte) ((left >> 8) & 0xFF);
|
||||
if (soundType) {
|
||||
ret[retPos + 2] = (byte) (right & 0xFF);
|
||||
ret[retPos + 3] = (byte) ((right >> 8) & 0xFF);
|
||||
}
|
||||
} else {
|
||||
ret[retPos] = (byte) (left & 0xFF);
|
||||
if (soundType) {
|
||||
ret[retPos + 1] = (byte) (right & 0xFF);
|
||||
}
|
||||
}
|
||||
prevLeft = left;
|
||||
prevRight = right;
|
||||
retPos += sampleLen;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,7 +810,8 @@ public class SoundImporter {
|
||||
}
|
||||
|
||||
int pos = -1;
|
||||
loopChars: for (SoundTag tag : soundTags) {
|
||||
loopChars:
|
||||
for (SoundTag tag : soundTags) {
|
||||
pos++;
|
||||
int characterId = tag.getCharacterId();
|
||||
List<File> existingFilesForSoundTag = new ArrayList<>();
|
||||
@@ -670,7 +857,7 @@ public class SoundImporter {
|
||||
for (SoundStreamFrameRange r : ranges.get(pos)) {
|
||||
for (File sourceFile : existingFilesForSoundTag) {
|
||||
if (sourceFile.getName().startsWith("" + characterId + "_" + (r.startFrame + 1) + "-")) {
|
||||
|
||||
|
||||
try {
|
||||
if (printOut) {
|
||||
System.out.println("Importing character " + characterId + ", start frame " + r.startFrame + " from file " + sourceFile.getName());
|
||||
|
||||
@@ -61,5 +61,3 @@ configurationFile.meta.showComments = Show configuration comments - set to true
|
||||
configurationFile.meta.modifiedOnly = Store modified items only in this file - set to false (and exit app again to resave) to show all.
|
||||
configurationFile.configuration = Section - Actual configuration
|
||||
configuration.removed = WARNING: This configuration was REMOVED. It is unused.
|
||||
|
||||
exception.soundFormat.expected = Required format for import to this sound stream: %expected%\nFormat of selected file:%actual%\nPlease convert the file to the required format and try again.
|
||||
|
||||
@@ -62,5 +62,3 @@ configurationFile.meta.showComments = Zobrazit koment\u00e1\u0159e ke konfigurac
|
||||
configurationFile.meta.modifiedOnly = Ukl\u00e1dat do tohoto souboru pouze zm\u011bn\u011bn\u00e9 hodnoty - nastavte na false (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed v\u0161eho.
|
||||
configurationFile.configuration = Sekce - Vlastn\u00ed konfigurace
|
||||
configuration.removed = VAROV\u00c1N\u00cd: Tato konfigurace byla ODSTRAN\u011aNA. Nepou\u017e\u00edv\u00e1 se.
|
||||
|
||||
exception.soundFormat.expected = Vy\u017eadovan\u00fd form\u00e1t pro import do tohoto zvukov\u00e9ho streamu: %expected%\nForm\u00e1t vybran\u00e9ho souboru: %actual%\nPros\u00edm zkonvertujte soubor do vy\u017eadovan\u00e9ho form\u00e1tu a zkuste to znovu.
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2025 JPEXS, All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3.0 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.tags.base;
|
||||
|
||||
import com.jpexs.decompiler.flash.AppResources;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class SoundParametersMismatchException extends SoundImportException {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param expectedSoundType
|
||||
* @param expectedSoundSize
|
||||
* @param expectedSoundRate
|
||||
* @param expectedSoundFormat
|
||||
*/
|
||||
public SoundParametersMismatchException(
|
||||
boolean expectedSoundType,
|
||||
boolean expectedSoundSize,
|
||||
int expectedSoundRate,
|
||||
int expectedSoundFormat,
|
||||
boolean actualSoundType,
|
||||
boolean actualSoundSize,
|
||||
int actualSoundRate,
|
||||
int actualSoundFormat) {
|
||||
super(AppResources.translate("exception.soundFormat.expected").replace("%expected%",
|
||||
(expectedSoundType ? "stereo" : "mono") + " "
|
||||
+ (expectedSoundSize ? "16bit" : "8bit") + " "
|
||||
+ new int[]{5512, 11025, 22050, 44100}[expectedSoundRate] + " Hz"
|
||||
+ " " + new String[]{
|
||||
"uncompressed native endian",
|
||||
"adpcm",
|
||||
"mp3",
|
||||
"uncompressed little endian",
|
||||
"nellymoser 16 kHz",
|
||||
"nellymoser 8 kHz",
|
||||
"nellymoser",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"speex"
|
||||
}[expectedSoundFormat]).replace("%actual%",
|
||||
(actualSoundType ? "stereo" : "mono") + " "
|
||||
+ (actualSoundSize ? "16bit" : "8bit") + " "
|
||||
+ new int[]{5512, 11025, 22050, 44100}[actualSoundRate] + " Hz"
|
||||
+ " " + new String[]{
|
||||
"uncompressed native endian",
|
||||
"adpcm",
|
||||
"mp3",
|
||||
"uncompressed little endian",
|
||||
"nellymoser 16 kHz",
|
||||
"nellymoser 8 kHz",
|
||||
"nellymoser",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"speex"
|
||||
}[actualSoundFormat]));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,6 @@ public class MP3SOUNDDATA {
|
||||
long initLen = mis.getPosition();
|
||||
MarkingPushbackInputStream mpis = bitstream.getSource();
|
||||
while (true) {
|
||||
//System.err.println("initLen = "+initLen);
|
||||
long posBefore = initLen + mpis.getPosition();
|
||||
MP3FRAME frame = MP3FRAME.readFrame(bitstream, decoder);
|
||||
if (frame == null) {
|
||||
@@ -60,7 +59,7 @@ public class MP3SOUNDDATA {
|
||||
}
|
||||
long posAfter = initLen + mpis.getPosition();
|
||||
frame.setFullData(Arrays.copyOfRange(data, (int) posBefore, (int) posAfter));
|
||||
frames.add(frame);
|
||||
frames.add(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user