@@ -81,7 +84,7 @@
-
+
@@ -114,7 +117,7 @@
-
+
diff --git a/jpsxdec/doc/CHANGES.txt b/jpsxdec/doc/CHANGES.txt
index af13288..9e5d8c8 100644
--- a/jpsxdec/doc/CHANGES.txt
+++ b/jpsxdec/doc/CHANGES.txt
@@ -1,3 +1,18 @@
+v1.06 (beta) rev4156 (19 Feb 2022)
+ - Support for videos in game "N Gauge Unten Kibun Game: Gatan Goton"
+ - Support for videos in more Electronic Arts games
+ (Github issue #40 "Andretti Racing - Sector scanning fails with error message"
+ by BloodRaynare)
+ - More accurate FPS detection, but indexing is slower, sometimes SIGNIFICANTLY slower
+ - Slightly improved video encoder quality and accuracy
+ - Command-line option "-a audio" now defaults to skipping audio associated
+ with videos, and added additional option "-vidaud" to include all audio
+ (see manual for details)
+ (Github issue #42 "No Commandline Argument for skipping audio from video files"
+ by Meerkov)
+ - A lot of bug fixes and improved error handling
+ Known issues
+ - Translations not updated since v1.00
v1.05 (beta) rev4052 (14 Mar 2021)
- Support for Panekit - Infinitive Crafting Toy Case videos
- Support for Star Wars - Rebel Assault II - The Hidden Empire videos
diff --git a/jpsxdec/doc/CREDITS.txt b/jpsxdec/doc/CREDITS.txt
index ddeebcb..1304dd7 100644
--- a/jpsxdec/doc/CREDITS.txt
+++ b/jpsxdec/doc/CREDITS.txt
@@ -78,7 +78,7 @@ The countless people who created so many open source tools that I've used
in this project, and in my every day life. It is a huge list.
................................................................................
-Thanks to those who wrote code that I've used or referenced at some point
+Thanks to those who wrote code that I've used or referenced at some point
during development:
Alexander Strange for porting the ffmpeg simple_idct to Java.
diff --git a/jpsxdec/doc/LICENSE.txt b/jpsxdec/doc/LICENSE.txt
index 7506dde..ac1d922 100644
--- a/jpsxdec/doc/LICENSE.txt
+++ b/jpsxdec/doc/LICENSE.txt
@@ -1,6 +1,6 @@
jPSXdec: PlayStation 1 Media Decoder/Converter in Java
-Copyright (C) 2007-2021 Michael Sabin
+Copyright (C) 2007-2022 Michael Sabin
All rights reserved.
jPSXdec is licensed as a whole under this non-commercial license:
diff --git a/jpsxdec/doc/jPSXdec-manual.odt b/jpsxdec/doc/jPSXdec-manual.odt
index 958c473..b13d92a 100644
Binary files a/jpsxdec/doc/jPSXdec-manual.odt and b/jpsxdec/doc/jPSXdec-manual.odt differ
diff --git a/jpsxdec/doc/lgpl-2.1.txt b/jpsxdec/doc/lgpl-2.1.txt
index 602bfc9..84f5523 100644
--- a/jpsxdec/doc/lgpl-2.1.txt
+++ b/jpsxdec/doc/lgpl-2.1.txt
@@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
-
+
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
@@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
-
+
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
-
+
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
@@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
-
+
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
@@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
-
+
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
@@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
-
+
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
@@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
-
+
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
-
+
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
@@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
-
+
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
diff --git a/jpsxdec/lib/apache-ant-1.9.16-bin.zip b/jpsxdec/lib/apache-ant-1.9.16-bin.zip
new file mode 100644
index 0000000..531f8c3
Binary files /dev/null and b/jpsxdec/lib/apache-ant-1.9.16-bin.zip differ
diff --git a/jpsxdec/lib/apache-ant-1.9.16-src.zip b/jpsxdec/lib/apache-ant-1.9.16-src.zip
new file mode 100644
index 0000000..d286049
Binary files /dev/null and b/jpsxdec/lib/apache-ant-1.9.16-src.zip differ
diff --git a/jpsxdec/lib/hamcrest-core-1.3-sources.jar b/jpsxdec/lib/hamcrest-core-1.3-sources.jar
new file mode 100644
index 0000000..c3c110b
Binary files /dev/null and b/jpsxdec/lib/hamcrest-core-1.3-sources.jar differ
diff --git a/jpsxdec/lib/hamcrest-core-1.3.jar b/jpsxdec/lib/hamcrest-core-1.3.jar
new file mode 100644
index 0000000..9d5fe16
Binary files /dev/null and b/jpsxdec/lib/hamcrest-core-1.3.jar differ
diff --git a/jpsxdec/lib/junit-4.13.2-sources.jar b/jpsxdec/lib/junit-4.13.2-sources.jar
new file mode 100644
index 0000000..c6b46f6
Binary files /dev/null and b/jpsxdec/lib/junit-4.13.2-sources.jar differ
diff --git a/jpsxdec/lib/junit-4.13.2.jar b/jpsxdec/lib/junit-4.13.2.jar
new file mode 100644
index 0000000..6da55d8
Binary files /dev/null and b/jpsxdec/lib/junit-4.13.2.jar differ
diff --git a/jpsxdec/src/jpsxdec/LogToConsole.properties b/jpsxdec/src/jpsxdec/LogToConsole.properties
index c03235b..3ae4486 100644
--- a/jpsxdec/src/jpsxdec/LogToConsole.properties
+++ b/jpsxdec/src/jpsxdec/LogToConsole.properties
@@ -3,4 +3,4 @@ handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter=jpsxdec.i18n.log.DebugFormatter
java.util.logging.ConsoleHandler.level=ALL
-#jpsxdec.level=ALL
\ No newline at end of file
+#jpsxdec.level=ALL
diff --git a/jpsxdec/src/jpsxdec/LogToFile.properties b/jpsxdec/src/jpsxdec/LogToFile.properties
index c9ea101..dd974b4 100644
--- a/jpsxdec/src/jpsxdec/LogToFile.properties
+++ b/jpsxdec/src/jpsxdec/LogToFile.properties
@@ -7,4 +7,4 @@ java.util.logging.FileHandler.formatter=jpsxdec.i18n.log.DebugFormatter
java.util.logging.FileHandler.count=1
java.util.logging.FileHandler.level=ALL
-jpsxdec.level=INFO
\ No newline at end of file
+jpsxdec.level=INFO
diff --git a/jpsxdec/src/jpsxdec/Main.java b/jpsxdec/src/jpsxdec/Main.java
index cdb85d3..1bb1704 100644
--- a/jpsxdec/src/jpsxdec/Main.java
+++ b/jpsxdec/src/jpsxdec/Main.java
@@ -37,11 +37,10 @@
package jpsxdec;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
+import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import jpsxdec.cmdline.CommandLine;
@@ -54,36 +53,16 @@ public class Main {
private static final Logger LOG = Logger.getLogger(Main.class.getName());
- private static boolean loadLoggerConfigFromSystemProperty() {
+ /** @see LogManager */
+ private static boolean isLoggerConfiguredFromProperty() {
String sLogConfigFile = System.getProperty("java.util.logging.config.file");
- if (sLogConfigFile == null)
- return false;
+ if (sLogConfigFile != null)
+ return true;
+ String sLogConfigClass = System.getProperty("java.util.logging.config.class");
+ if (sLogConfigClass != null)
+ return true;
- FileInputStream fis;
- try {
- fis = new FileInputStream(sLogConfigFile);
- } catch (FileNotFoundException ex) {
- ex.printStackTrace();
- return false;
- }
-
- boolean blnSuccess = false;
-
- try {
- java.util.logging.LogManager.getLogManager().readConfiguration(fis);
- blnSuccess = true;
- } catch (Exception ex) {
- ex.printStackTrace();
- blnSuccess = false;
- } finally {
- try {
- fis.close();
- } catch (IOException ex1) {
- ex1.printStackTrace();
- blnSuccess = false;
- }
- }
- return blnSuccess;
+ return false;
}
public static void loadDefaultLogger() {
@@ -107,7 +86,7 @@ public static void loadLoggerConfigResource(@Nonnull Class> referenceClass,
/** Main entry point to the jPSXdec program. */
public static void main(final String[] asArgs) {
- if (!loadLoggerConfigFromSystemProperty())
+ if (!isLoggerConfiguredFromProperty())
loadDefaultLogger();
ArgParser ap = new ArgParser(asArgs);
diff --git a/jpsxdec/src/jpsxdec/Version.java b/jpsxdec/src/jpsxdec/Version.java
index c9c5741..094124d 100644
--- a/jpsxdec/src/jpsxdec/Version.java
+++ b/jpsxdec/src/jpsxdec/Version.java
@@ -39,7 +39,7 @@
public class Version {
- public final static String Version = "1.05 (beta)";
+ public final static String Version = "1.06 (beta)";
public final static String IndexHeader = "[jPSXdec v"+Version+"]";
}
diff --git a/jpsxdec/src/jpsxdec/adpcm/AudioShortReader.java b/jpsxdec/src/jpsxdec/adpcm/AudioShortReader.java
deleted file mode 100644
index ded6800..0000000
--- a/jpsxdec/src/jpsxdec/adpcm/AudioShortReader.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * jPSXdec: PlayStation 1 Media Decoder/Converter in Java
- * Copyright (C) 2016-2020 Michael Sabin
- * All rights reserved.
- *
- * Redistribution and use of the jPSXdec code or any derivative works are
- * permitted provided that the following conditions are met:
- *
- * * Redistributions may not be sold, nor may they be used in commercial
- * or revenue-generating business activities.
- *
- * * Redistributions that are modified from the original source must
- * include the complete source code, including the source code for all
- * components used by a binary built from the modified sources. However, as
- * a special exception, the source code distributed need not include
- * anything that is normally distributed (in either source or binary form)
- * with the major components (compiler, kernel, and so on) of the operating
- * system on which the executable runs, unless that component itself
- * accompanies the executable.
- *
- * * Redistributions must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
- * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package jpsxdec.adpcm;
-
-import java.io.Closeable;
-import java.io.IOException;
-import javax.annotation.Nonnull;
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
-import jpsxdec.util.IO;
-import jpsxdec.util.IncompatibleException;
-
-/** Wraps {@link AudioInputStream} to read mono or stereo 16-bit
- * {@link AudioFormat.Encoding.PCM_SIGNED} samples as arrays of shorts. */
-public class AudioShortReader implements Closeable {
-
- @Nonnull
- private final AudioInputStream _sourceAudio;
-
- private long _lngSampleFramesRead = 0;
-
- /** If source stream is ended. */
- private boolean _blnIsEof = false;
-
- public AudioShortReader(@Nonnull AudioInputStream sourceAudio)
- throws IncompatibleException
- {
- AudioFormat fmt = sourceAudio.getFormat();
- int iChannels = fmt.getChannels();
- if (fmt.getEncoding() != AudioFormat.Encoding.PCM_SIGNED)
- throw new IncompatibleException("Encoding must be PCM_SIGNED");
- if (fmt.getSampleSizeInBits() != 16)
- throw new IncompatibleException("Bit size must be 16 " + fmt.getSampleRate());
- if (iChannels < 1 || iChannels > 2)
- throw new IncompatibleException("Channels must be 1 or 2");
-
- _sourceAudio = sourceAudio;
- }
-
- /** If source stream is ended. */
- public boolean isEof() {
- return _blnIsEof;
- }
-
- /** Closes the underlying audio stream. */
- @Override
- public void close() throws IOException {
- _sourceAudio.close();
- }
-
- public long getSampleFramesRead() {
- return _lngSampleFramesRead;
- }
-
- /** Read the requested number of sample frames from the input source stream
- * and convert to 1 (mono) or 2 (stereo) arrays of shorts.
- * If end of stream is reached, fills remaining
- * samples with 0 and {@link #isEof()} will return true.
- *
- * @param iSampleFrameCount The number of sample frames to read
- * (1 sample frame = 1 short for mono or 2 shorts for stereo).
- *
- * @return 1 or 2 arrays of {@code iSampleFrameCount} shorts. */
- public @Nonnull short[][] readSoundUnitSamples(int iSampleFrameCount) throws IOException {
- int iChannels = _sourceAudio.getFormat().getChannels();
- byte[] abReadPcmSoundUnitSamples = new byte[iSampleFrameCount*2*iChannels];
- short[][] aasiPcmSoundUnitChannelSamples = new short[iChannels][iSampleFrameCount];
-
- int iStart = 0;
- int iLen = abReadPcmSoundUnitSamples.length;
- while (iLen > 0) {
- int iRead = _sourceAudio.read(abReadPcmSoundUnitSamples, iStart, iLen);
- if (iRead < 0) {
- _blnIsEof = true;
- break;
- }
- _lngSampleFramesRead += iRead;
- iLen -= iRead;
- }
-
- boolean blnIsBigEndian = _sourceAudio.getFormat().isBigEndian();
- for (int iInByte = 0, iOutSample = 0;
- iInByte < abReadPcmSoundUnitSamples.length;
- iOutSample++)
- {
- for (int iChannel = 0; iChannel < iChannels; iChannel++, iInByte+=2) {
- aasiPcmSoundUnitChannelSamples[iChannel][iOutSample] =
- blnIsBigEndian ?
- IO.readSInt16BE(abReadPcmSoundUnitSamples, iInByte) :
- IO.readSInt16LE(abReadPcmSoundUnitSamples, iInByte);
- }
- }
- return aasiPcmSoundUnitChannelSamples;
- }
-
-}
diff --git a/jpsxdec/src/jpsxdec/adpcm/SoundUnitEncoder.java b/jpsxdec/src/jpsxdec/adpcm/SoundUnitEncoder.java
index 1a4f624..eee1b17 100644
--- a/jpsxdec/src/jpsxdec/adpcm/SoundUnitEncoder.java
+++ b/jpsxdec/src/jpsxdec/adpcm/SoundUnitEncoder.java
@@ -37,7 +37,6 @@
package jpsxdec.adpcm;
-import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
@@ -59,30 +58,38 @@ public class SoundUnitEncoder {
/** Encoded ADPCM sound unit. */
public static class EncodedUnit {
/** Sound parameter filter used to encode. */
- public final int iFilterIndex;
+ private final int _iFilterIndex;
/** Sound parameter range used to encode. */
- public final int iRange;
+ private final int _iRange;
/** If at least 1 ADPCM sample had to be clamped to fit. */
- public final boolean blnHadToClamp;
-
- /** Encoded ADPCM data samples.
- * 8 bits/sample uses the whole byte.
- * 4 bits/sample uses the least significant nibble. */
+ private final boolean _blnHadToClamp;
+ /** @see #getByteOrNibble(int) */
@Nonnull
- public final byte[] abEncodedAdpcm;
+ private final byte[] _abEncodedAdpcm;
private EncodedUnit(int iFilterIndex, int iRange, boolean blnHadToClamp,
@Nonnull byte[] abEncodedAdpcm)
{
- this.iFilterIndex = iFilterIndex;
- this.iRange = iRange;
- this.blnHadToClamp = blnHadToClamp;
- this.abEncodedAdpcm = abEncodedAdpcm;
+ _iFilterIndex = iFilterIndex;
+ _iRange = iRange;
+ _blnHadToClamp = blnHadToClamp;
+ _abEncodedAdpcm = abEncodedAdpcm;
}
+
public byte getSoundParameter() {
- return (byte) (((iFilterIndex & 0xf) << 4) | (iRange & 0xf));
+ return (byte) (((_iFilterIndex & 0xf) << 4) | (_iRange & 0xf));
}
+ /** Encoded ADPCM data samples.
+ * 8 bits/sample uses the whole byte.
+ * 4 bits/sample uses the least significant nibble. */
+ public byte getByteOrNibble(int i) {
+ return _abEncodedAdpcm[i];
+ }
+
+ public boolean hadToClamp() {
+ return _blnHadToClamp;
+ }
}
/** Encoding equivalent to {@link AdpcmContext}. */
@@ -152,15 +159,14 @@ public SoundUnitEncoder(int iAdpcmBitsPerSample, @Nonnull K0K1Filter filters) {
/** Encodes the PCM samples into an {@link EncodedUnit}.
* Searches for the sound parameters with the best possible result.
- * @param asiPcmSoundUnitSamples A {@link SoundUnitDecoder#SAMPLES_PER_SOUND_UNIT}
- * worth of 16-bit samples.
+ * @param asi28PcmSamples A {@link SoundUnitDecoder#SAMPLES_PER_SOUND_UNIT}
+ * worth of 16-bit samples.
* @param loggingContext Object that will be used to give some context when logging.
*/
- @Nonnull EncodedUnit encodeSoundUnit(@Nonnull short[] asiPcmSoundUnitSamples,
+ @Nonnull EncodedUnit encodeSoundUnit(@Nonnull short[] asi28PcmSamples,
@Nonnull IContextCopier loggingContext)
- throws IOException
{
- if (asiPcmSoundUnitSamples.length != SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT)
+ if (asi28PcmSamples.length != SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT)
throw new IllegalArgumentException();
FilterRangeEncoder best = null;
@@ -175,7 +181,7 @@ public SoundUnitEncoder(int iAdpcmBitsPerSample, @Nonnull K0K1Filter filters) {
if (iRange == 0 && iFilterIdx == 0)
zeroRangeFilter = encTry;
- if (encTry.encode(asiPcmSoundUnitSamples, loggingContext))
+ if (encTry.encode(asi28PcmSamples, loggingContext))
continue;
if (best == null || encTry.isBetterThan(best)) {
@@ -206,7 +212,6 @@ public SoundUnitEncoder(int iAdpcmBitsPerSample, @Nonnull K0K1Filter filters) {
private @Nonnull EncodedUnit encodeSoundUnit(@Nonnull short[] asiPcmSoundUnitSamples,
int iFilterIdx, int iRange,
@Nonnull IContextCopier loggingContext)
- throws IOException
{
if (asiPcmSoundUnitSamples.length != SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT)
throw new IllegalArgumentException();
@@ -230,7 +235,6 @@ public SoundUnitEncoder(int iAdpcmBitsPerSample, @Nonnull K0K1Filter filters) {
@Nonnull EncodedUnit encodeSoundUnit(@Nonnull short[] asiPcmSoundUnitSamples,
int iParameters,
@Nonnull IContextCopier loggingContext)
- throws IOException
{
if (iParameters < 0)
throw new IllegalArgumentException();
diff --git a/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmEncoder.java b/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmEncoder.java
index aaa0672..bd5e7b5 100644
--- a/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmEncoder.java
+++ b/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmEncoder.java
@@ -38,12 +38,12 @@
package jpsxdec.adpcm;
import java.io.Closeable;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
-import javax.sound.sampled.AudioInputStream;
+import jpsxdec.formats.Signed16bitLittleEndianLinearPcmAudioInputStream;
import jpsxdec.util.IO;
import jpsxdec.util.IncompatibleException;
@@ -52,7 +52,7 @@ public abstract class SpuAdpcmEncoder implements Closeable {
/** Source audio stream. */
@Nonnull
- protected final AudioShortReader _audioShortReader;
+ protected final Signed16bitLittleEndianLinearPcmAudioInputStream _audioShortReader;
/** Running ADPCM encoders(s). */
@Nonnull
@@ -92,9 +92,8 @@ public String toString() {
@CheckForNull
protected InputStream _presetPrameters = null;
- protected SpuAdpcmEncoder(@Nonnull AudioInputStream input) throws IncompatibleException {
- // will verify all format details but stereo are valid
- _audioShortReader = new AudioShortReader(input);
+ protected SpuAdpcmEncoder(@Nonnull Signed16bitLittleEndianLinearPcmAudioInputStream input) throws IncompatibleException {
+ _audioShortReader = input;
// SPU ADPCM is always 4 bits per sample
_leftOrMonoEncoder = new SoundUnitEncoder(4, K0K1Filter.SPU);
@@ -102,10 +101,6 @@ protected SpuAdpcmEncoder(@Nonnull AudioInputStream input) throws IncompatibleEx
abstract public boolean isStereo();
- public boolean isEof() {
- return _audioShortReader.isEof();
- }
-
@Override
public void close() throws IOException {
_audioShortReader.close();
@@ -123,10 +118,10 @@ public void setPresetParameters(@CheckForNull InputStream presetParameters) {
public static class Mono extends SpuAdpcmEncoder {
- public Mono(@Nonnull AudioInputStream input) throws IncompatibleException {
+ public Mono(@Nonnull Signed16bitLittleEndianLinearPcmAudioInputStream input) throws IncompatibleException {
super(input);
- if (input.getFormat().getChannels() != 1)
+ if (input.isStereo())
throw new IncompatibleException();
}
@@ -135,18 +130,19 @@ public boolean isStereo() {
return false;
}
- /** @see Stereo#encode1SoundUnit(byte, java.io.OutputStream, byte, java.io.OutputStream) */
- public boolean encode1SoundUnit(byte bFlagBits, @Nonnull OutputStream spuOutputStream)
- throws IOException
+ /** @see Stereo#encode1SoundUnit(byte, byte) */
+ public @Nonnull byte[] encode1SoundUnit(byte bFlagBits)
+ throws EOFException, IOException
{
- if (_audioShortReader.isEof())
- return false;
short[][] aasiPcmSoundUnitChannelSamples =
- _audioShortReader.readSoundUnitSamples(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
+ _audioShortReader.readSampleFrames(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
- encode1SoundUnitChannel(_leftOrMonoEncoder, aasiPcmSoundUnitChannelSamples[0],
- bFlagBits, spuOutputStream);
- return true;
+ _logContext.iChannel = 0;
+ byte[] abEncodedAdpcm = encode1SoundUnitChannel(_leftOrMonoEncoder, bFlagBits, aasiPcmSoundUnitChannelSamples[0]);
+ _logContext.iChannel = -1;
+ _logContext.lngSampleFramesReadEncoded += SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT;
+
+ return abEncodedAdpcm;
}
}
@@ -157,10 +153,10 @@ public static class Stereo extends SpuAdpcmEncoder {
@Nonnull
private final SoundUnitEncoder _rightChannel;
- public Stereo(@Nonnull AudioInputStream input) throws IncompatibleException {
+ public Stereo(@Nonnull Signed16bitLittleEndianLinearPcmAudioInputStream input) throws IncompatibleException {
super(input);
- if (input.getFormat().getChannels() != 2)
+ if (!input.isStereo())
throw new IncompatibleException();
_rightChannel = new SoundUnitEncoder(4, K0K1Filter.SPU);
@@ -176,55 +172,56 @@ public boolean isStereo() {
* ({@link SoundUnitDecoder#SAMPLES_PER_SOUND_UNIT}
* to the supplied output streams.
*
- * 28 sample frames will be read from the source audio stream
- * and 16 bytes will be written to each output stream, setting
- * the flag bits to the given value.
- *
- * Stops encoding anything if the source audio is empty.
- * @return if the source audio stream is empty. */
- public boolean encode1SoundUnit(byte bLeftFlagBits, @Nonnull OutputStream leftSpuStream,
- byte bRightFlagBits, @Nonnull OutputStream rightSpuStream)
- throws IOException
+ * 28 sample frames will be read from the underlying source audio stream
+ * and return two arrays of 16 bytes, setting the flag bits to the given values. */
+ public byte[][] encode1SoundUnit(byte bLeftFlagBits, byte bRightFlagBits)
+ throws EOFException, IOException
{
- if (_audioShortReader.isEof())
- return false;
-
short[][] aasiPcmSoundUnitChannelSamples =
- _audioShortReader.readSoundUnitSamples(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
+ _audioShortReader.readSampleFrames(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
+
+ byte[][] aabEncodedAdpcm = new byte[2][];
_logContext.iChannel = 0;
- encode1SoundUnitChannel(_leftOrMonoEncoder, aasiPcmSoundUnitChannelSamples[_logContext.iChannel],
- bLeftFlagBits, leftSpuStream);
+ aabEncodedAdpcm[_logContext.iChannel] = encode1SoundUnitChannel(_leftOrMonoEncoder, bLeftFlagBits, aasiPcmSoundUnitChannelSamples[_logContext.iChannel]);
_logContext.iChannel = 1;
- encode1SoundUnitChannel(_rightChannel, aasiPcmSoundUnitChannelSamples[_logContext.iChannel],
- bRightFlagBits, rightSpuStream);
+ aabEncodedAdpcm[_logContext.iChannel] = encode1SoundUnitChannel(_rightChannel, bRightFlagBits, aasiPcmSoundUnitChannelSamples[_logContext.iChannel]);
_logContext.iChannel = -1;
_logContext.lngSampleFramesReadEncoded += SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT;
- return true;
+
+ return aabEncodedAdpcm;
}
}
- protected void encode1SoundUnitChannel(@Nonnull SoundUnitEncoder encoder,
- @Nonnull short[] asiPcmSoundUnitChannelSamples,
- byte bFlagBits, @Nonnull OutputStream spuStream)
- throws IOException
+ protected byte[] encode1SoundUnitChannel(@Nonnull SoundUnitEncoder encoder,
+ byte bFlagBits,
+ @Nonnull short[] asi28PcmChannelSamples)
{
SoundUnitEncoder.EncodedUnit encoded;
if (_presetPrameters == null) {
- encoded = encoder.encodeSoundUnit(
- asiPcmSoundUnitChannelSamples, _logContext);
+ encoded = encoder.encodeSoundUnit(asi28PcmChannelSamples, _logContext);
} else {
- encoded = encoder.encodeSoundUnit(
- asiPcmSoundUnitChannelSamples,
- _presetPrameters.read(), _logContext);
+ int iParameter;
+ try {
+ iParameter = IO.readSInt8(_presetPrameters);
+ } catch (IOException ex) {
+ throw new RuntimeException("Error reading input parameter", ex);
+ }
+ encoded = encoder.encodeSoundUnit(asi28PcmChannelSamples, iParameter, _logContext);
}
- spuStream.write(encoded.getSoundParameter() & 0xff);
- spuStream.write(bFlagBits & 0xff);
- for (int i = 0; i < SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; i+=2) {
- IO.writeInt4x2(spuStream, encoded.abEncodedAdpcm[i+1], encoded.abEncodedAdpcm[i]);
+ byte[] abEncoded = new byte[SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT];
+ abEncoded[0] = encoded.getSoundParameter();
+ abEncoded[1] = bFlagBits;
+ for (int iIn = 0, iOut = 2; iIn < SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; iIn+=2, iOut++) {
+ int iTop4Bits = encoded.getByteOrNibble(iIn + 1) & 0xf;
+ int iBottom4Bits = encoded.getByteOrNibble(iIn ) & 0xf;
+ int iByte = (iTop4Bits << 4) | iBottom4Bits;
+
+ abEncoded[iOut] = (byte) iByte;
}
+ return abEncoded;
}
}
diff --git a/jpsxdec/src/jpsxdec/adpcm/VagWriter.java b/jpsxdec/src/jpsxdec/adpcm/VagWriter.java
index 82e43da..a394654 100644
--- a/jpsxdec/src/jpsxdec/adpcm/VagWriter.java
+++ b/jpsxdec/src/jpsxdec/adpcm/VagWriter.java
@@ -49,7 +49,7 @@
/** Writes a VAG ("very audio good") file.
*
- * VAG files seem to be a common format involved in PSX development.
+ * VAG files seem to be a common format involved in PlayStation 1 development.
* It contains a single sound clip of SPU audio.
*
* The file contains the following data.
@@ -101,7 +101,7 @@ public static boolean isValidId(@Nonnull String sId) {
private int _iWrittenSoundUnitCount = 0;
/** {@link #VagWriter(java.io.File, java.lang.String, int)} with the
- * string filename converted to {@ File}. */
+ * string filename converted to {@link File}. */
public VagWriter(@Nonnull String sOutputFile, @Nonnull String sId, int iSampleRate)
throws FileNotFoundException, IOException
{
diff --git a/jpsxdec/src/jpsxdec/adpcm/XaAdpcmEncoder.java b/jpsxdec/src/jpsxdec/adpcm/XaAdpcmEncoder.java
index db945e3..be1199b 100644
--- a/jpsxdec/src/jpsxdec/adpcm/XaAdpcmEncoder.java
+++ b/jpsxdec/src/jpsxdec/adpcm/XaAdpcmEncoder.java
@@ -37,14 +37,14 @@
package jpsxdec.adpcm;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
+import jpsxdec.formats.Signed16bitLittleEndianLinearPcmAudioInputStream;
import jpsxdec.util.IO;
import jpsxdec.util.IncompatibleException;
@@ -54,18 +54,15 @@
* The input is an audio stream at a matching quality
* (16 bits/sample at 37800 Hz or 18900 Hz in mono or stereo).
* The output is the "user data" for a Mode 2 Form 2 real-time XA audio sector.
- *
- * Will encode audio as long as source data is available. If there is no
- * more source data, silence is encoded. Check for EOF condition with
- * {@link #isEof()}. */
+ * more source data, silence is encoded. Check for EOF condition with. */
public class XaAdpcmEncoder implements Closeable {
/** Source audio stream. */
@Nonnull
- private final AudioShortReader _audioShortReader;
+ private final Signed16bitLittleEndianLinearPcmAudioInputStream _audioShortReader;
/** Sample rate of source/output stream. Must be 18900 or 37800 Hz */
- private final int _iSampleRate;
+ private final int _iSampleFramesPerSecond;
/** true=encode to 4 bits/sample, false=encode to 8 bits/sample. */
private final boolean _blnEncode4BitsElse8Bits;
@@ -88,10 +85,9 @@ public static class LogContext implements IContextCopier {
public int iSoundGroup = -1;
/** The sound unit being encoded. */
public int iSoundUnit = -1;
- /** Audio channel being encoded (0 or 1). */
+ /** Left/right audio channel being encoded (0 or 1). */
public int iChannel = -1;
/** The number of PCM sample frames that have been read
- * (i.e. a stereo sample frame is only 1 sample frame).
* Used to help find where in the input stream data was being encoded. */
public long lngSamplesFramesRead = 0;
@@ -120,7 +116,7 @@ public String toString() {
* via a stream of bytes. Set by {@link #setPresetParameters(java.io.InputStream)}.
* Primarily for development/testing purposes. */
@CheckForNull
- private InputStream _presetPrameters = null;
+ private InputStream _presetParameters = null;
/** Create a new encoder for the given audio stream, to be encoded with the
* given bits.
@@ -128,7 +124,7 @@ public String toString() {
* @param iEncodeToAdpcmBitsPerSample Must be 4 or 8
* @throws UnsupportedOperationException if source audio format is incorrect.
*/
- public XaAdpcmEncoder(@Nonnull AudioInputStream ais, int iEncodeToAdpcmBitsPerSample)
+ public XaAdpcmEncoder(@Nonnull Signed16bitLittleEndianLinearPcmAudioInputStream ais, int iEncodeToAdpcmBitsPerSample)
throws IncompatibleException
{
if (iEncodeToAdpcmBitsPerSample == 4)
@@ -138,18 +134,16 @@ else if (iEncodeToAdpcmBitsPerSample == 8)
else // programmer error
throw new IllegalArgumentException("Invalid encoding bits/sample "+iEncodeToAdpcmBitsPerSample+", must be 4 or 8");
- AudioFormat fmt = ais.getFormat();
- if (Math.abs(fmt.getSampleRate() - 37800) > 0.1f)
- _iSampleRate = 37800;
- else if (Math.abs(fmt.getSampleRate() - 18900) > 0.1f)
- _iSampleRate = 18900;
+ if (ais.getSampleFramesPerSecond() == 37800)
+ _iSampleFramesPerSecond = 37800;
+ else if (ais.getSampleFramesPerSecond() == 18900)
+ _iSampleFramesPerSecond = 18900;
else
- throw new IncompatibleException("Unsupported sample rate "+fmt.getSampleRate()+", must be 18900 or 37800");
+ throw new IncompatibleException("Unsupported sample rate "+ais.getSampleFramesPerSecond()+", must be 18900 or 37800");
- // will verify all other format details are valid
- _audioShortReader = new AudioShortReader(ais);
+ _audioShortReader = ais;
- int iChannels = fmt.getChannels();
+ int iChannels = ais.isStereo() ? 2 : 1;
_aoEncoders = new SoundUnitEncoder[iChannels];
for (int i = 0; i < iChannels; i++) {
_aoEncoders[i] = new SoundUnitEncoder(iEncodeToAdpcmBitsPerSample,
@@ -161,12 +155,8 @@ public boolean isStereo() {
return _aoEncoders.length == 2;
}
- public int getSampleRate() {
- return _iSampleRate;
- }
-
- public boolean isEof() {
- return _audioShortReader.isEof();
+ public int getSampleFramesPerSecond() {
+ return _iSampleFramesPerSecond;
}
@Override
@@ -177,27 +167,31 @@ public void close() throws IOException {
/** Manually provide the filter and range parameters for every Sound Unit
* via a stream of bytes. Primarily for development/testing purposes. */
public void setPresetParameters(@CheckForNull InputStream presetParameters) {
- _presetPrameters = presetParameters;
+ _presetParameters = presetParameters;
}
/** From the source audio stream, reads either 4032 sample frames
* (for 4 bits/sample), or 2016 sample frames (for 8 bits/sample).
- * And writes 2304 bytes to the provided output stream. Note that the
+ * And returns 2304 bytes. Note that the
* last 20 bytes of the sector are not included in the output.
- * If the end of the audio stream is reached, silence will be written
- * for the remainder to the output. */
- public void encode1Sector(@Nonnull OutputStream os) throws IOException {
+ * @throws EOFException if the underlying audio input stream stream is exhausted
+ * @throws IOException if there is an error reading from the underlying audio input stream
+ */
+ public @Nonnull byte[] encode1Sector() throws EOFException, IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(2304);
for (_logContext.iSoundGroup = 0;
_logContext.iSoundGroup < XaAdpcmDecoder.ADPCM_SOUND_GROUPS_PER_SECTOR;
_logContext.iSoundGroup++)
{
- encodeSoundGroup(os);
+ encodeSoundGroup(baos);
}
_logContext.iSoundGroup = -1;
_logContext.iEncodedSectorCount++;
+
+ return baos.toByteArray();
}
- private void encodeSoundGroup(@Nonnull OutputStream os) throws IOException {
+ private void encodeSoundGroup(@Nonnull ByteArrayOutputStream os) throws EOFException, IOException {
SoundUnitEncoder.EncodedUnit[] aoEncoded;
if (_blnEncode4BitsElse8Bits)
aoEncoded = new SoundUnitEncoder.EncodedUnit[XaAdpcmDecoder.SOUND_UNITS_IN_4_BIT_SOUND_GROUP];
@@ -206,19 +200,25 @@ private void encodeSoundGroup(@Nonnull OutputStream os) throws IOException {
for (_logContext.iSoundUnit = 0; _logContext.iSoundUnit < aoEncoded.length;) {
short[][] aasiPcmSoundUnitChannelSamples =
- _audioShortReader.readSoundUnitSamples(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
+ _audioShortReader.readSampleFrames(SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT);
for (_logContext.iChannel = 0;
_logContext.iChannel < _aoEncoders.length;
_logContext.iChannel++, _logContext.iSoundUnit++)
{
- if (_presetPrameters == null) {
+ if (_presetParameters == null) {
aoEncoded[_logContext.iSoundUnit] = _aoEncoders[_logContext.iChannel].encodeSoundUnit(
aasiPcmSoundUnitChannelSamples[_logContext.iChannel], _logContext);
} else {
+ int iParameter;
+ try {
+ iParameter = IO.readUInt8(_presetParameters);
+ } catch (IOException ex) {
+ throw new RuntimeException("Error reading encoding parameter", ex);
+ }
aoEncoded[_logContext.iSoundUnit] = _aoEncoders[_logContext.iChannel].encodeSoundUnit(
aasiPcmSoundUnitChannelSamples[_logContext.iChannel],
- _presetPrameters.read(), _logContext);
+ iParameter, _logContext);
}
}
_logContext.iChannel = -1;
@@ -246,10 +246,10 @@ private void encodeSoundGroup(@Nonnull OutputStream os) throws IOException {
// 1/0,3/2,5/4,7/6, 1/0,3/2,5/4,7/6, ...
for (int i = 0; i < SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; i++) {
- IO.writeInt4x2(os, aoEncoded[1].abEncodedAdpcm[i], aoEncoded[0].abEncodedAdpcm[i]);
- IO.writeInt4x2(os, aoEncoded[3].abEncodedAdpcm[i], aoEncoded[2].abEncodedAdpcm[i]);
- IO.writeInt4x2(os, aoEncoded[5].abEncodedAdpcm[i], aoEncoded[4].abEncodedAdpcm[i]);
- IO.writeInt4x2(os, aoEncoded[7].abEncodedAdpcm[i], aoEncoded[6].abEncodedAdpcm[i]);
+ writeInt4x2(os, aoEncoded[1].getByteOrNibble(i), aoEncoded[0].getByteOrNibble(i));
+ writeInt4x2(os, aoEncoded[3].getByteOrNibble(i), aoEncoded[2].getByteOrNibble(i));
+ writeInt4x2(os, aoEncoded[5].getByteOrNibble(i), aoEncoded[4].getByteOrNibble(i));
+ writeInt4x2(os, aoEncoded[7].getByteOrNibble(i), aoEncoded[6].getByteOrNibble(i));
}
} else {
// aoEncoded.length == AdpcmSoundGroup.SOUND_UNITS_IN_8_BIT_SOUND_GROUP == 4
@@ -263,13 +263,17 @@ private void encodeSoundGroup(@Nonnull OutputStream os) throws IOException {
// 0,1,2,3, 0,1,2,3, 0,1,2,3 ...
for (int i = 0; i < SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; i++) {
- os.write(aoEncoded[0].abEncodedAdpcm[i]);
- os.write(aoEncoded[1].abEncodedAdpcm[i]);
- os.write(aoEncoded[2].abEncodedAdpcm[i]);
- os.write(aoEncoded[3].abEncodedAdpcm[i]);
+ os.write(aoEncoded[0].getByteOrNibble(i));
+ os.write(aoEncoded[1].getByteOrNibble(i));
+ os.write(aoEncoded[2].getByteOrNibble(i));
+ os.write(aoEncoded[3].getByteOrNibble(i));
}
}
}
+ private static void writeInt4x2(@Nonnull ByteArrayOutputStream stream, byte bTop4bits, byte bBottom4bits) {
+ stream.write(((bTop4bits&0xf)<<4) | (bBottom4bits&0xf));
+ }
+
}
diff --git a/jpsxdec/src/jpsxdec/adpcm/package.html b/jpsxdec/src/jpsxdec/adpcm/package.html
index b3f858a..d9c383e 100644
--- a/jpsxdec/src/jpsxdec/adpcm/package.html
+++ b/jpsxdec/src/jpsxdec/adpcm/package.html
@@ -5,4 +5,4 @@
ADPCM decoding and encoding.