diff --git a/jpsxdec/PlayStation1_STR_format.txt b/jpsxdec/PlayStation1_STR_format.txt index 62c8e71..00d8ea2 100644 --- a/jpsxdec/PlayStation1_STR_format.txt +++ b/jpsxdec/PlayStation1_STR_format.txt @@ -1,6 +1,6 @@ The PlayStation 1 Video (STR) Format v1.11, ? 2017? - http://jpsxdec.kenai.com/ + https://github.com/m35/jpsxdec http://jpsxdec.blogspot.com/ -------------------------------------------------------------------------------- diff --git a/jpsxdec/TODO.txt b/jpsxdec/TODO.txt index 9e48752..d51c2a7 100644 --- a/jpsxdec/TODO.txt +++ b/jpsxdec/TODO.txt @@ -67,14 +67,10 @@ Improvements Java 6 Console Background -Java <= 5 only has stdout PrintStream, -which does not allow choosing the text encoder (for non-English languages) +Java <= 5 only had stdout PrintStream, which does not allow choosing the text +encoder (for non-English languages) Suggested solution -Use Java 6 Console to use proper encoding when printing to the console -Java 6 added 'Console' that offers the choice of encoding -jPSXdec uses Java 5, however reflection can get around that -The printing to the console can be abstracted and reflection could not only save -Faith's sister Kate but also gain access to Console if Java >= 6, otherwise fall back to PrintStream +Use Java 6 Console to use proper encoding when printing to the console. GUI improvements * Only show disc and index dropdown button when there are list entries @@ -127,8 +123,6 @@ Add list box when indexing that will show the items as they are discovered ? Make tutorial on ripping a game and using jpsxdec ? Update manual to cover game ripping -Remove .mov from STR extensions -Give up trying to verify CD sector header "File" should have any value Log "Writing dup frame for frame" to user log with "to keep audio and video in sync" Log "Ahead of reading by x frames" more like "out of sync with frame timing" maybe? "Frame in sectors 129921-129927 is missing chunk 6" -> "Frame found in sectors x-x is incomplete or missing data" @@ -154,11 +148,10 @@ Serious Real-time media playback/preview issues * OpenJDK Ubuntu video playback doesn't process frames * Oracle Java Ubuntu VM audio auto-starts when it shouldn't jPSXdec still has issues in several cases - ! Bug: Audio that starts way before, ends way after, or has long breaks is not handled by player (ff tactics FFTST.STR) + /?/ Bug: Audio that starts way before, ends way after, or has long breaks is not handled by player (ff tactics FFTST.STR) * Bug: when audio doesn't start for a prolonged period, video frame queue fills and blocks - * Add PlayingState.AT_END - * Bug: Player doesn't let you play movie with 1 frame - * Bug: video player repeats prior frames and audio when frame type is not detected + /?/ Bug: Player doesn't let you play movie with 1 frame + /?/ Bug: video player repeats prior frames and audio when frame type is not detected * Pause video-only playback when reading is delayed /?/ Bug: end of playing race condition locks gui /?/ Bug: Player automatically plays video-only content when it is very short @@ -186,17 +179,24 @@ Games to investigate * Discworld 2 * BrainDead 13 * Syphon Filter 3 -* Policenauts - Video in static files * "Love Game's - Wai Wai Tennis [Service Price] [SLPS-01647] have intro.str file weighing 33 megabytes. In jpsxdec did not see." * Fear Effect +* Kowloon's Gate +* Jackie Chan Stuntmaster +* Firemen 2 +* Magic the Gathering +* Aconcagua ending video +// Policenauts - Video in static files Replace - * Crusader audio - * SPU - Then introduce a shared -replace command with a prompt before applying changes which can be skipped with -y (don't prompt to apply changes) or -n (essentially a dry-run) + * SPU + * Add ability to replace using a PNG with (semi-)transparent pixels + which would be painted over the original frame, then re-encoded + x Crusader audio + x Then introduce a shared -replace command Editing disc items * SPU items samples/second varies by clip @@ -237,8 +237,6 @@ Create my own smaller/simpler arg parser General code stuff * Add unit tests to build.xml - * Unit tests require Java 6 while jPSXdec is built with Java 5 ... - can use 2 different Javas in ant? * Create unit test subproject? * Clean up all the TODOs * Proper handling for InterruptedExcetion @@ -249,22 +247,16 @@ General code stuff Add @Override all the things Logger framework that supports thread context logging - removes all the log parameters -Replace playback system with ExecutorService -SectorClaimer is not thread safe! setRangeLimit() mutates the global instance - -* Super simplify but slow down IO.java ! Cleanup Main: Remove FeedbackStream and just use a Logger -* show error/warn unused cmdline args +/ show error/warn unused cmdline args -! Have separate DiscItemBuilderGuiFactory to create a gui from a DiscItemBuilder and cache it +* Have separate DiscItemBuilderGuiFactory to create a gui from a DiscItemBuilder and cache it * Log when frame rate detection fails -* change xa playback to properly handle static (adjacent sector) streaming +* change xa playback to better handle static (adjacent sector) streaming * Figure out how to better organize all the RGB and YCbCr pixel formats -? Use ConcurrentLinkedQueue in player queue wrapped with bounds - ? Optimize AviWriter with buffers - Construct audio/video chunks in ram before writing - Write to buffered stream @@ -277,7 +269,6 @@ SectorClaimer is not thread safe! setRangeLimit() mutates the global instance may remove the possibility of random access, unless there is smart limited scope contextual detection considering adjacent sectors -? Can the 0 AC remover count them then have single message per frame? "Invalid Crusader header 00000000000000000000000000000000" Any way to check where in the stream it is? @@ -311,8 +302,6 @@ Video encoder Game specific * Finish testing LoD iki: GOAST.IKI (end of disc 2), DENIN2.INI (middle of disc 3), DRAGON1.IKI - * Add WCW to sector test - * Update LoM sector test * Finish Crusader tests . only audio . starts near end @@ -338,7 +327,7 @@ Game specific ? Even when not upsampling chroma, utilize it for PsxYuv->JpgYuv matrix conversion ! Test how much difference there is between the two color sets ? When displaying progress, setup a timer to only update the display at most every second -? Index doesn't indicate generic type that is used in cmdline save all +* Index doesn't indicate generic type that is used in cmdline save all /?/ Add LIST:INFO:ISFT to AVI ? Save and load gui ini on every change? ? https://github.com/Georgeto/XFileDialog @@ -349,6 +338,8 @@ Game specific Done or rejected /x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x +//Remove .mov from STR extensions +//Give up trying to verify CD sector header "File" should have any value // Indexing overhaul: //Introduce contextual information to help identify sector types //Identifying video sectors by combining the frame data along the way and checking if its valid (FF7, Lain, Dredd) @@ -357,8 +348,6 @@ x Change visualization to output HTML page -> no, 20mb .html files are bad x Finish function "identifySector()" in DiscItem to more quickly figure out what type of sectors are read (since it won't have to go through the whole list of like 10 types). -// Add partial replace tests -// Add in a comment in log what sector types were found during indexing (for debugging) x Change sector reading to start by only reading the header, then read remaining as needed -> like to avoid handling possible ioexception everywhere x PSX may not like replaced tims, check palette effect -> removed bmp tim replace @@ -374,37 +363,9 @@ x PSX may not like replaced tims, check palette effect -> removed bmp tim replac // unfortunately RandomAccessFile cannot be extended like this // use threads x otherwise stuck with a state machine -// Verify Loggers and LogRecords and ResourceBundles, use another log system? -> my own logger -// Finish RbGen // Localize Main help -// Finish XA encoder -// Look into adding ErrorHandler to my Loggers -> my own logger -// Ensure cleaning AC=0 codes doesn't affect decoded output -// Try to find audio from Gran Turismo -// Remove need for video frame dimensions to be in sector -// Demuxer should provide frame dimensions either from the sector or from checking the demuxed frame -// Applies to: Dredd, Gran Turismo 2, Crusader already does this? -// XA replace: give better feedback what stopped the replace -// Save isofile: display output size for raw/normal / Add time log msg at end of save/index -// Update launch4j -// Update dir chooser - project is dead -//Indexing - // ISO indexer keeps a bit array of all sectors in the CD and flags them as mode 1/2. - //When creating the DiscItemISO9660Files, it checks if the file contains mode 2 sectors -// Duplicated frame number in header messes things up (iki) - // Remove need for frame numbers, and Dredd prebuilt index - x Just store the frame start sectors for Dredd - x Make Dredd indexer and Dredd Disc item that subclasses DiscItemVideoStream -// Localize disc item help -// Change the way video items split audio items: do it after indexing. - //It's conceptually better, and removes Audio indexer from having to know about video frames. - //However, different movie types split movies slightly differently - //And right now its the sectors that are the only game specific logic - //I would need to add game specific indexers -x Add doxygen to build.xml - x Make doxygen config -x Bug: Tree music icon is hard to see on blue background +// Can the 0 AC remover count them then have single message per frame? ------------------------------------------ User test feedback diff --git a/jpsxdec/build.xml b/jpsxdec/build.xml index d9d2aff..23ceb84 100644 --- a/jpsxdec/build.xml +++ b/jpsxdec/build.xml @@ -9,7 +9,7 @@ - + @@ -43,8 +43,8 @@ - - + + @@ -62,7 +62,7 @@ - Be sure to set JAVA_HOME to 1.5 jdk directory before building. + Be sure to set JAVA_HOME to 1.6 jdk directory before building. - Be sure to set JAVA_HOME to 1.5 jdk directory before building. + Be sure to set JAVA_HOME to 1.6 jdk directory before building. - + Be sure junit jar (at least verion 4) is in the classpath or local directory or build will fail. @@ -196,22 +196,6 @@ Manually convert manual to pdf using [Open|Libre]Office to continue build - - - - - - - - - - - - - - diff --git a/jpsxdec/doc/CHANGES.txt b/jpsxdec/doc/CHANGES.txt index 875c964..86ec512 100644 --- a/jpsxdec/doc/CHANGES.txt +++ b/jpsxdec/doc/CHANGES.txt @@ -1,3 +1,24 @@ +v0.99.9 rev3898 (9 Aug 2019) + - Added Italian translation (thanks Gianluigi "Infrid" Cusimano!) + - Updated Spanish translation (still many thanks to Víctor González, Sergi Medina!) + - Minimum required Java version is now version 6 + - Updated to be compatible with Java version 9 and higher + - Added support for ReBoot video (thanks to XBrav!) + - Added support for Policenauts FMVs + - Added support for Road Rash 3D FMVs + - Added support for opening video of Aconcagua + - Improved real-time audio/video player on Linux (hopefully) + Bug fixes: + - Couldn't save 24-bit TIM images as png + - Videos with odd frame dimensions caused errors + - Lots of small fixes + Internal: + - Revamped bitstream handling + - Several simplifications + Known issues: + - Videos with variable frames rates saved as AVI do not play right in + some video players (primarily on Windows). Use a more reliable player + such as VLC media player. v0.99.8 rev3788 (21 Jan 2019) - Updated Spanish translation (still many thanks to Víctor González, Sergi Medina) - Updated Japanese translation (still using Google translate) @@ -34,7 +55,7 @@ v0.99.8 rev3788 (21 Jan 2019) - Frame numbering redesign Known issues: - Video playback on Linux may still not work right - - Videos with variable frames rates saved as AVI do not play right in the + - Videos with variable frames rates saved as AVI do not play right in the default video players found in some Windows versions. Use a more reliable player such VLC media player. v0.99.7 rev3397 (8 Jan 2017) The "road to v1.0" release diff --git a/jpsxdec/doc/CREDITS.txt b/jpsxdec/doc/CREDITS.txt index fbcaffb..ddeebcb 100644 --- a/jpsxdec/doc/CREDITS.txt +++ b/jpsxdec/doc/CREDITS.txt @@ -68,6 +68,10 @@ Sony's official SDK movconv tool. Víctor González and Sergi Medina for the Spanish translation. +Gianluigi "Infrid" Cusimano for adding the Italian translation. + +XBrav for adding support for Reboot video. + The Hitmen for releasing invaluable source code related to PSX hacking. The countless people who created so many open source tools that I've used diff --git a/jpsxdec/doc/LICENSE.txt b/jpsxdec/doc/LICENSE.txt index 0cd4a65..03c5f51 100644 --- a/jpsxdec/doc/LICENSE.txt +++ b/jpsxdec/doc/LICENSE.txt @@ -53,10 +53,15 @@ Some individual components are licensed under compatible licenses: The jPSXdec translations Specifically src/jpsxdec/i18n/I.java and Translations*.properties -Copyright Michael Sabin, Víctor González, Sergi Medina +Copyright: + Michael Sabin + Víctor González + Sergi Medina + Gianluigi "Infrid" Cusimano English and Japanese by Michael Sabin Spanish by Víctor González and Sergi Medina +Italian by Gianluigi "Infrid" Cusimano Licensed under the Apache License, Version 2.0 The text of this license is contained in the file apache-2.0.txt diff --git a/jpsxdec/doc/jPSXdec-manual.odt b/jpsxdec/doc/jPSXdec-manual.odt index 3503d4a..7fd91ec 100644 Binary files a/jpsxdec/doc/jPSXdec-manual.odt and b/jpsxdec/doc/jPSXdec-manual.odt differ diff --git a/jpsxdec/jpsxdec.exe b/jpsxdec/jpsxdec.exe index 90d0c29..30750c2 100644 Binary files a/jpsxdec/jpsxdec.exe and b/jpsxdec/jpsxdec.exe differ diff --git a/jpsxdec/launch4j.xml b/jpsxdec/launch4j.xml index bcae9ff..6a1e8a1 100644 --- a/jpsxdec/launch4j.xml +++ b/jpsxdec/launch4j.xml @@ -18,7 +18,7 @@ false false - 1.5.0 + 1.6.0 preferJre 64/32 diff --git a/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeTableUI.java b/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeTableUI.java deleted file mode 100644 index 534f7b5..0000000 --- a/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeTableUI.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 2.1 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; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package com.sun.java.swing.plaf.windows; - -import java.awt.Color; -import java.awt.Graphics; - - -/** - * Performance improvement for {@link org.jdesktop.swingx.JXTreeTable}. - *

- * The infamous JTreeTable. Nearly all Java tree-tables are based on an - * example published by Sun many years ago. The trick is to render just a - * small rectangle of the {@link javax.swing.JTree} component in each cell. - * With the cells stacked up, it looks like one continuous component. - *

- * To render each rectangle, {@link Graphics#translate(int, int)} is called - * when the JTree returned by {@link javax.swing.table.TableCellRenderer} is - * painted. This is functional in theory, but Windows has some serious - * performance side-effects when painting with a translation. Now combine - * that with painting the tree dotted lines one dot at a time as the - * {@link javax.swing.plaf.basic.BasicTreeUI} does, and the tree-table becomes - * painfully slow. - *

- * This little hack overrides the dotted line paint - * call with a single solid line paint call. Performance is improved - * significantly. - *

- * This class is instantiated dynamically on Windows and passed to - * {@link org.jdesktop.swingx.JXTreeTable#setUI(javax.swing.plaf.TableUI)}. - *

- * I also tried {@link java.awt.Graphics2D#setStroke(java.awt.Stroke)} to a dotted line, but the - * performance was still abysmal. - * - * @author Michael - */ -public class WindowsTreeTableUI extends WindowsTreeUI { - - @Override - protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2) { - Color c = g.getColor(); - g.setColor(c.brighter()); - g.drawLine(x1, y, x2, y); - g.setColor(c); - } - - @Override - protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) { - Color c = g.getColor(); - g.setColor(c.brighter()); - g.drawLine(x, y1, x, y2); - g.setColor(c); - } - -} \ No newline at end of file diff --git a/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeUI.java b/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeUI.java deleted file mode 100644 index e175d2b..0000000 --- a/jpsxdec/src-lgpl/com/sun/java/swing/plaf/windows/WindowsTreeUI.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 2.1 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; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package com.sun.java.swing.plaf.windows; - -import javax.swing.plaf.basic.BasicTreeUI; - -/** - * A dummy class just so jPSXdec will build on non-Windows platforms. - * On Windows, it will be ignored and the JRE's will be used instead. - * - * @see WindowsTreeTableUI - * - * @author Michael - */ -public class WindowsTreeUI extends BasicTreeUI { - -} diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTable.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTable.java index dfd7903..11fe02a 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTable.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTable.java @@ -533,7 +533,7 @@ public JXTable(int numRows, int numColumns) { * @param rowData Row data, as a Vector of Objects. * @param columnNames Column names, as a Vector of Strings. */ - public JXTable(Vector rowData, Vector columnNames) { + public JXTable(Vector rowData, Vector columnNames) { super(rowData, columnNames); init(); } diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTreeTable.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTreeTable.java index de42eec..f98a9a9 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTreeTable.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/JXTreeTable.java @@ -36,7 +36,6 @@ import java.util.Enumeration; import java.util.EventObject; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ActionMap; @@ -58,7 +57,6 @@ import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeWillExpandListener; -import javax.swing.plaf.TreeUI; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; @@ -2809,18 +2807,6 @@ protected void setExpandedState(TreePath path, boolean state) { } - @Override - public void setUI(TreeUI ui) { - if (ui.getClass().getName().equals("com.sun.java.swing.plaf.windows.WindowsTreeUI")) { - try { - ui = (TreeUI) Class.forName("com.sun.java.swing.plaf.windows.WindowsTreeTableUI").newInstance(); - } catch (Exception ex) { - Logger.getLogger(JXTreeTable.class.getName()).log(Level.SEVERE, null, ex); - } - } - super.setUI(ui); - } - /** * updateUI is overridden to set the colors of the Tree's renderer * to match that of the table. diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/LookAndFeelAddons.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/LookAndFeelAddons.java index 24f92d4..e50bdbe 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/LookAndFeelAddons.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/LookAndFeelAddons.java @@ -28,9 +28,9 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; -import javax.imageio.spi.ServiceRegistry; import javax.swing.JComponent; import javax.swing.LookAndFeel; @@ -258,8 +258,8 @@ public static String getBestMatchAddonClassName() { } else if (UIManager.getSystemLookAndFeelClassName().equals(laf.getClass().getName())) { className = getSystemAddonClassName(); } else { - Iterator addonLoader = ServiceRegistry.lookupProviders(LookAndFeelAddons.class, - getClassLoader()); + Iterator addonLoader = ServiceLoader.load(LookAndFeelAddons.class, + getClassLoader()).iterator(); while (addonLoader.hasNext()) { LookAndFeelAddons addon = addonLoader.next(); @@ -297,8 +297,8 @@ public String run() { * @return the addon matching the native operating system platform. */ public static String getSystemAddonClassName() { - Iterator addonLoader = ServiceRegistry.lookupProviders(LookAndFeelAddons.class, - getClassLoader()); + Iterator addonLoader = ServiceLoader.load(LookAndFeelAddons.class, + getClassLoader()).iterator(); String className = null; while (addonLoader.hasNext()) { diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/BasicTransferable.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/BasicTransferable.java index 557acbb..f86a555 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/BasicTransferable.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/BasicTransferable.java @@ -144,7 +144,7 @@ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorExcepti } else if (Reader.class.equals(flavor.getRepresentationClass())) { return new StringReader(data); } else if (InputStream.class.equals(flavor.getRepresentationClass())) { - return new StringBufferInputStream(data); + return new ByteArrayInputStream(data.getBytes("US-ASCII")); } // fall through to unsupported } else if (isPlainFlavor(flavor)) { @@ -155,7 +155,7 @@ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorExcepti } else if (Reader.class.equals(flavor.getRepresentationClass())) { return new StringReader(data); } else if (InputStream.class.equals(flavor.getRepresentationClass())) { - return new StringBufferInputStream(data); + return new ByteArrayInputStream(data.getBytes("US-ASCII")); } // fall through to unsupported diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/LazyActionMap.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/LazyActionMap.java index 6b2bd90..a9f5393 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/LazyActionMap.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/plaf/basic/core/LazyActionMap.java @@ -136,6 +136,7 @@ public void setParent(ActionMap map) { super.setParent(map); } + @SuppressWarnings("unchecked") private void loadIfNecessary() { if (_loader != null) { Object loader = _loader; diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/DefaultVisuals.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/DefaultVisuals.java index ab255c3..e0dab8c 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/DefaultVisuals.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/DefaultVisuals.java @@ -119,7 +119,7 @@ public void configureVisuals(T renderingComponent, CellContext context) { */ protected void configurePainter(T renderingComponent, CellContext context) { if (renderingComponent instanceof PainterAware) { - ((PainterAware) renderingComponent).setPainter(null); + ((PainterAware) renderingComponent).setPainter(null); } } diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererCheckBox.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererCheckBox.java index ab8fdd4..866776c 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererCheckBox.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererCheckBox.java @@ -38,13 +38,13 @@ * * @author Jeanette Winzenburg */ -public class JRendererCheckBox extends JCheckBox implements PainterAware { - protected Painter painter; +public class JRendererCheckBox extends JCheckBox implements PainterAware { + protected Painter painter; /** * {@inheritDoc} */ - public Painter getPainter() { + public Painter getPainter() { return painter; } @@ -52,7 +52,7 @@ public Painter getPainter() { /** * {@inheritDoc} */ - public void setPainter(Painter painter) { + public void setPainter(Painter painter) { Painter old = getPainter(); this.painter = painter; if (painter != null) { diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererLabel.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererLabel.java index 8be392a..3affd74 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererLabel.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/JRendererLabel.java @@ -61,9 +61,9 @@ * * @author Jeanette Winzenburg */ -public class JRendererLabel extends JLabel implements PainterAware, IconAware { +public class JRendererLabel extends JLabel implements PainterAware, IconAware { - protected Painter painter; + protected Painter painter; /** * @@ -100,7 +100,7 @@ public boolean isOpaque() { /** * {@inheritDoc} */ - public void setPainter(Painter painter) { + public void setPainter(Painter painter) { Painter old = getPainter(); this.painter = painter; firePropertyChange("painter", old, getPainter()); @@ -109,7 +109,7 @@ public void setPainter(Painter painter) { /** * {@inheritDoc} */ - public Painter getPainter() { + public Painter getPainter() { return painter; } /** diff --git a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/PainterAware.java b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/PainterAware.java index 43df74a..4947520 100644 --- a/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/PainterAware.java +++ b/jpsxdec/src-lgpl/org/jdesktop/swingx/renderer/PainterAware.java @@ -30,7 +30,7 @@ * * @author Jeanette Winzenburg */ -public interface PainterAware { - void setPainter(Painter painter); - Painter getPainter(); +public interface PainterAware { + void setPainter(Painter painter); + Painter getPainter(); } diff --git a/jpsxdec/src/argparser/ArgParser.java b/jpsxdec/src/argparser/ArgParser.java index 3d84447..dcabad1 100644 --- a/jpsxdec/src/argparser/ArgParser.java +++ b/jpsxdec/src/argparser/ArgParser.java @@ -380,7 +380,7 @@ */ public class ArgParser { - Vector matchList; + Vector matchList; // int tabSpacing = 8; String synopsisString; boolean helpOptionsEnabled = true; @@ -979,7 +979,7 @@ public ArgParser(String synopsisString) */ public ArgParser(String synopsisString, boolean defaultHelp) { - matchList = new Vector(128); + matchList = new Vector(128); this.synopsisString = synopsisString; if (defaultHelp) { addOption ("-help,-? %h #displays help information", null); @@ -1794,7 +1794,7 @@ private Object createResultHolder (Record rec) return null; // can't happen } - static void stringToArgs (Vector vec, String s, + static void stringToArgs (Vector vec, String s, boolean allowQuotedStrings) throws StringScanException { @@ -1830,7 +1830,7 @@ public static String[] prependArgs (Reader reader, String[] args) { args = new String[0]; } LineNumberReader lineReader = new LineNumberReader (reader); - Vector vec = new Vector(100, 100); + Vector vec = new Vector(100, 100); String line; int i, k; @@ -1969,7 +1969,7 @@ public void matchAllArgs (String[] args) */ public String[] matchAllArgs (String[] args, int idx, int exitFlags) { - Vector unmatched = new Vector(10); + Vector unmatched = new Vector(10); while (idx < args.length) { try @@ -2031,6 +2031,7 @@ public String[] matchAllArgs (String[] args, int idx, int exitFlags) * @see ArgParser#getErrorMessage * @see ArgParser#getUnmatchedArgument */ + @SuppressWarnings("unchecked") public int matchArg (String[] args, int idx) throws ArgParseException { @@ -2089,7 +2090,7 @@ else if (rec.convertCode != 'v') } } if (rec.resHolder instanceof Vector) - { ((Vector)rec.resHolder).add (result); + { ((Vector)rec.resHolder).add (result); } } catch (ArgParseException e) diff --git a/jpsxdec/src/com/jhlabs/awt/ConstraintLayout.java b/jpsxdec/src/com/jhlabs/awt/ConstraintLayout.java index bf1ec80..8171542 100644 --- a/jpsxdec/src/com/jhlabs/awt/ConstraintLayout.java +++ b/jpsxdec/src/com/jhlabs/awt/ConstraintLayout.java @@ -30,7 +30,7 @@ public class ConstraintLayout implements LayoutManager2 { protected int hMargin = 0; protected int vMargin = 0; - private Hashtable constraints; + private Hashtable constraints; protected boolean includeInvisible = false; public void addLayoutComponent(String constraint, Component c) { @@ -49,7 +49,7 @@ public void removeLayoutComponent(Component c) { public void setConstraint(Component c, Object constraint) { if (constraint != null) { if (constraints == null) - constraints = new Hashtable(); + constraints = new Hashtable(); constraints.put(c, constraint); } else if (constraints != null) constraints.remove(c); diff --git a/jpsxdec/src/com/l2fprod/common/swing/JDirectoryChooserBeanInfo.java b/jpsxdec/src/com/l2fprod/common/swing/JDirectoryChooserBeanInfo.java index 6af3441..5f6ca10 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/JDirectoryChooserBeanInfo.java +++ b/jpsxdec/src/com/l2fprod/common/swing/JDirectoryChooserBeanInfo.java @@ -57,7 +57,7 @@ public JDirectoryChooserBeanInfo() throws java.beans.IntrospectionException { * @return The additionalBeanInfo value */ public BeanInfo[] getAdditionalBeanInfo() { - Vector bi = new Vector(); + Vector bi = new Vector(); BeanInfo[] biarr = null; try { for (Class cl = com.l2fprod.common.swing.JDirectoryChooser.class @@ -119,7 +119,7 @@ public Image getIcon(int type) { */ public PropertyDescriptor[] getPropertyDescriptors() { try { - Vector descriptors = new Vector(); + Vector descriptors = new Vector(); PropertyDescriptor descriptor = null; try { diff --git a/jpsxdec/src/com/l2fprod/common/swing/PercentLayout.java b/jpsxdec/src/com/l2fprod/common/swing/PercentLayout.java index 3d8f85d..ca5a7eb 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/PercentLayout.java +++ b/jpsxdec/src/com/l2fprod/common/swing/PercentLayout.java @@ -94,7 +94,7 @@ public float floatValue() { private int orientation; private int gap; - private Hashtable m_ComponentToConstraint; + private Hashtable m_ComponentToConstraint; /** * Creates a new HORIZONTAL PercentLayout with a gap of 0. @@ -107,7 +107,7 @@ public PercentLayout(int orientation, int gap) { setOrientation(orientation); this.gap = gap; - m_ComponentToConstraint = new Hashtable(); + m_ComponentToConstraint = new Hashtable(); } public void setGap(int gap) { @@ -145,7 +145,7 @@ public Constraint getConstraint(Component component) { public void setConstraint(Component component, Object constraints) { if (constraints instanceof Constraint) { - m_ComponentToConstraint.put(component, constraints); + m_ComponentToConstraint.put(component, (Constraint)constraints); } else if (constraints instanceof Number) { setConstraint( component, @@ -334,7 +334,7 @@ public void layoutContainer(Container parent) { } // finally share the remaining space between the other components - ArrayList remaining = new ArrayList(); + ArrayList remaining = new ArrayList(); for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { Constraint constraint = @@ -348,8 +348,8 @@ public void layoutContainer(Container parent) { if (remaining.size() > 0) { int rest = availableSize / remaining.size(); - for (Iterator iter = remaining.iterator(); iter.hasNext();) { - sizes[((Integer)iter.next()).intValue()] = rest; + for (Iterator iter = remaining.iterator(); iter.hasNext();) { + sizes[iter.next().intValue()] = rest; } } diff --git a/jpsxdec/src/com/l2fprod/common/swing/PercentLayoutAnimator.java b/jpsxdec/src/com/l2fprod/common/swing/PercentLayoutAnimator.java index 37da807..9f27116 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/PercentLayoutAnimator.java +++ b/jpsxdec/src/com/l2fprod/common/swing/PercentLayoutAnimator.java @@ -33,7 +33,7 @@ public class PercentLayoutAnimator implements ActionListener { private Timer animatorTimer; - private List tasks = new ArrayList(); + private List tasks = new ArrayList(); private PercentLayout layout; private Container container; diff --git a/jpsxdec/src/com/l2fprod/common/swing/plaf/AbstractComponentAddon.java b/jpsxdec/src/com/l2fprod/common/swing/plaf/AbstractComponentAddon.java index 84c8c23..71f6cfa 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/plaf/AbstractComponentAddon.java +++ b/jpsxdec/src/com/l2fprod/common/swing/plaf/AbstractComponentAddon.java @@ -51,7 +51,7 @@ public void uninitialize(LookAndFeelAddons addon) { * @param addon * @param defaults */ - protected void addBasicDefaults(LookAndFeelAddons addon, List defaults) { + protected void addBasicDefaults(LookAndFeelAddons addon, List defaults) { } /** @@ -60,7 +60,7 @@ protected void addBasicDefaults(LookAndFeelAddons addon, List defaults) { * @param addon * @param defaults */ - protected void addMacDefaults(LookAndFeelAddons addon, List defaults) { + protected void addMacDefaults(LookAndFeelAddons addon, List defaults) { addBasicDefaults(addon, defaults); } @@ -70,7 +70,7 @@ protected void addMacDefaults(LookAndFeelAddons addon, List defaults) { * @param addon * @param defaults */ - protected void addMetalDefaults(LookAndFeelAddons addon, List defaults) { + protected void addMetalDefaults(LookAndFeelAddons addon, List defaults) { addBasicDefaults(addon, defaults); } @@ -80,7 +80,7 @@ protected void addMetalDefaults(LookAndFeelAddons addon, List defaults) { * @param addon * @param defaults */ - protected void addMotifDefaults(LookAndFeelAddons addon, List defaults) { + protected void addMotifDefaults(LookAndFeelAddons addon, List defaults) { addBasicDefaults(addon, defaults); } @@ -90,7 +90,7 @@ protected void addMotifDefaults(LookAndFeelAddons addon, List defaults) { * @param addon * @param defaults */ - protected void addWindowsDefaults(LookAndFeelAddons addon, List defaults) { + protected void addWindowsDefaults(LookAndFeelAddons addon, List defaults) { addBasicDefaults(addon, defaults); } @@ -118,7 +118,7 @@ protected void addWindowsDefaults(LookAndFeelAddons addon, List defaults) { * */ private Object[] getDefaults(LookAndFeelAddons addon) { - List defaults = new ArrayList(); + List defaults = new ArrayList(); if (isWindows(addon)) { addWindowsDefaults(addon, defaults); } else if (isMetal(addon)) { @@ -142,7 +142,7 @@ private Object[] getDefaults(LookAndFeelAddons addon) { * Adds the all keys/values from the given named resource bundle to the * defaults */ - protected void addResource(List defaults, String bundleName) { + protected void addResource(List defaults, String bundleName) { ResourceBundle bundle = ResourceBundle.getBundle(bundleName); for (Enumeration keys = bundle.getKeys(); keys.hasMoreElements(); ) { String key = (String)keys.nextElement(); diff --git a/jpsxdec/src/com/l2fprod/common/swing/plaf/JDirectoryChooserAddon.java b/jpsxdec/src/com/l2fprod/common/swing/plaf/JDirectoryChooserAddon.java index 53ac9a3..cb50bdc 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/plaf/JDirectoryChooserAddon.java +++ b/jpsxdec/src/com/l2fprod/common/swing/plaf/JDirectoryChooserAddon.java @@ -33,7 +33,7 @@ public JDirectoryChooserAddon() { super("JDirectoryChooser"); } - protected void addBasicDefaults(LookAndFeelAddons addon, List defaults) { + protected void addBasicDefaults(LookAndFeelAddons addon, List defaults) { defaults.addAll(Arrays.asList(new Object[]{ JDirectoryChooser.UI_CLASS_ID, "com.l2fprod.common.swing.plaf.windows.WindowsDirectoryChooserUI", diff --git a/jpsxdec/src/com/l2fprod/common/swing/plaf/LookAndFeelAddons.java b/jpsxdec/src/com/l2fprod/common/swing/plaf/LookAndFeelAddons.java index 3816caa..4bac639 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/plaf/LookAndFeelAddons.java +++ b/jpsxdec/src/com/l2fprod/common/swing/plaf/LookAndFeelAddons.java @@ -59,7 +59,7 @@ */ public class LookAndFeelAddons { - private static List contributedComponents = new ArrayList(); + private static List contributedComponents = new ArrayList(); /** * Key used to ensure the current UIManager has been populated by the @@ -273,7 +273,7 @@ public static ComponentUI getUI(JComponent component, Class expectedUIClass) { return ui; } else { String realUI = ui.getClass().getName(); - Class realUIClass; + Class realUIClass; try { realUIClass = expectedUIClass.getClassLoader() .loadClass(realUI); diff --git a/jpsxdec/src/com/l2fprod/common/swing/plaf/windows/WindowsDirectoryChooserUI.java b/jpsxdec/src/com/l2fprod/common/swing/plaf/windows/WindowsDirectoryChooserUI.java index c191d25..4348202 100644 --- a/jpsxdec/src/com/l2fprod/common/swing/plaf/windows/WindowsDirectoryChooserUI.java +++ b/jpsxdec/src/com/l2fprod/common/swing/plaf/windows/WindowsDirectoryChooserUI.java @@ -324,13 +324,13 @@ private void findFile(File fileToLocate, boolean selectFile, boolean reload) { // split the full path into individual files to locate them in // the tree // model - List files = new ArrayList(); + List files = new ArrayList(); files.add(file); while ((file = chooser.getFileSystemView().getParentDirectory(file)) != null) { files.add(0, file); } - List path = new ArrayList(); + List path = new ArrayList(); // start from the root DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getModel() @@ -457,7 +457,7 @@ private void setSelectedFiles() { return; } - List files = new ArrayList(); + List files = new ArrayList(); for (int i = 0, c = selectedPaths.length; i < c; i++) { LazyMutableTreeNode node = (LazyMutableTreeNode)selectedPaths[i] .getLastPathComponent(); @@ -639,7 +639,7 @@ private FileTreeNode[] getChildren() { chooser.getFileSystemView().getFiles( getFile(), chooser.isFileHidingEnabled()); - ArrayList nodes = new ArrayList(); + ArrayList nodes = new ArrayList(); // keep only directories, no "file" in the tree. if (files != null) { for (int i = 0, c = files.length; i < c; i++) { @@ -652,7 +652,7 @@ private FileTreeNode[] getChildren() { } } // sort directories, FileTreeNode implements Comparable - FileTreeNode[] result = (FileTreeNode[])nodes + FileTreeNode[] result = nodes .toArray(new FileTreeNode[0]); Arrays.sort(result); return result; @@ -712,7 +712,7 @@ private static synchronized void addToQueue(FileTreeNode node, JTree tree) { */ private static final class Queue extends Thread { - private volatile Stack nodes = new Stack(); + private volatile Stack nodes = new Stack(); private Object lock = new Object(); diff --git a/jpsxdec/src/com/l2fprod/common/util/ResourceManager.java b/jpsxdec/src/com/l2fprod/common/util/ResourceManager.java index f52de13..0baf102 100644 --- a/jpsxdec/src/com/l2fprod/common/util/ResourceManager.java +++ b/jpsxdec/src/com/l2fprod/common/util/ResourceManager.java @@ -27,7 +27,7 @@ */ public class ResourceManager { - static Map nameToRM = new HashMap(); + static Map nameToRM = new HashMap(); private ResourceBundle bundle; diff --git a/jpsxdec/src/jpsxdec/Version.java b/jpsxdec/src/jpsxdec/Version.java index 92e0dda..af46d68 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 = "0.99.8 (beta)"; + public final static String Version = "0.99.9 (beta)"; public final static String IndexHeader = "[jPSXdec v"+Version+"]"; } diff --git a/jpsxdec/src/jpsxdec/adpcm/PSoundPpl.java b/jpsxdec/src/jpsxdec/adpcm/PSoundPpl.java index f59af5b..f0bf544 100644 --- a/jpsxdec/src/jpsxdec/adpcm/PSoundPpl.java +++ b/jpsxdec/src/jpsxdec/adpcm/PSoundPpl.java @@ -220,6 +220,7 @@ public long getOffset() { return _lngDecryptedOffset; } + @Override public String toString() { return String.format("\"%s\" \"%s\" %02x%02x%02x%02x%02x? %08x -> %d", _sSourceFilePath, _sItemName, diff --git a/jpsxdec/src/jpsxdec/adpcm/SoundUnitDecoder.java b/jpsxdec/src/jpsxdec/adpcm/SoundUnitDecoder.java index afbd9a4..3cb53a0 100644 --- a/jpsxdec/src/jpsxdec/adpcm/SoundUnitDecoder.java +++ b/jpsxdec/src/jpsxdec/adpcm/SoundUnitDecoder.java @@ -148,6 +148,7 @@ public String sample(int iSample) { ); } + @Override public String toString() { return String.format("%s Filter.Range %d.%d", _loggingContext, diff --git a/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmSoundUnit.java b/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmSoundUnit.java index bfe1727..cb4c5b7 100644 --- a/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmSoundUnit.java +++ b/jpsxdec/src/jpsxdec/adpcm/SpuAdpcmSoundUnit.java @@ -112,8 +112,8 @@ public SpuAdpcmSoundUnit(@Nonnull byte[] abSource) { this(abSource, 0); } public SpuAdpcmSoundUnit(@Nonnull byte[] abSource, int iSourceOffset) { - if (iSourceOffset < 0 || iSourceOffset + SIZEOF_SOUND_UNIT >= abSource.length) - throw new IllegalArgumentException(); + if (iSourceOffset < 0 || iSourceOffset + SIZEOF_SOUND_UNIT > abSource.length) + throw new IllegalArgumentException("iSourceOffset out of bounds " + iSourceOffset); System.arraycopy(abSource, iSourceOffset, _abSoundUnit, 0, SIZEOF_SOUND_UNIT); checkRange(); diff --git a/jpsxdec/src/jpsxdec/cdreaders/CdFileSectorReader.java b/jpsxdec/src/jpsxdec/cdreaders/CdFileSectorReader.java index dae817b..996d4f9 100644 --- a/jpsxdec/src/jpsxdec/cdreaders/CdFileSectorReader.java +++ b/jpsxdec/src/jpsxdec/cdreaders/CdFileSectorReader.java @@ -52,7 +52,6 @@ import jpsxdec.i18n.exception.LocalizedDeserializationFail; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.util.IO; -import jpsxdec.util.IOException6; import jpsxdec.util.Misc; import jpsxdec.util.TaskCanceledException; @@ -91,7 +90,7 @@ public CdFileNotFoundException(@Nonnull File file, FileNotFoundException ex) { } /** Exception if there is an error reading from the CD file. */ - public static class CdReadException extends IOException6 { + public static class CdReadException extends IOException { @Nonnull private final File _file; @@ -106,7 +105,7 @@ public CdReadException(@Nonnull File file, IOException ex) { } } /** Exception if there is an error writing to the CD file. */ - public static class CdWriteException extends IOException6 { + public static class CdWriteException extends IOException { @Nonnull private final File _file; @@ -138,7 +137,7 @@ public long getFileSize() { /** Exception if there is an error re-opening the CD file * (like for write-access). */ - public static class CdReopenException extends IOException6 { + public static class CdReopenException extends IOException { @Nonnull private final File _file; diff --git a/jpsxdec/src/jpsxdec/cdreaders/CdSector.java b/jpsxdec/src/jpsxdec/cdreaders/CdSector.java index 188900e..55415cd 100644 --- a/jpsxdec/src/jpsxdec/cdreaders/CdSector.java +++ b/jpsxdec/src/jpsxdec/cdreaders/CdSector.java @@ -37,11 +37,11 @@ package jpsxdec.cdreaders; +import java.util.Arrays; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.util.ByteArrayFPIS; import jpsxdec.util.IO; -import jpsxdec.util.Misc; /** Represents a single sector on a CD. */ @@ -136,8 +136,8 @@ final public long getUserDataFilePointer() { /** Returns copy of the 'user data' portion of the sector. */ final public @Nonnull byte[] getCdUserDataCopy() { int iStart = _iByteStartOffset + getHeaderDataSize(); - return Misc.copyOfRange(_abSectorBytes, - iStart, iStart + getCdUserDataSize()); + return Arrays.copyOfRange(_abSectorBytes, + iStart, iStart + getCdUserDataSize()); } /** Copies a block of bytes out of the user data portion of this CD sector to the supplied array. @@ -155,9 +155,9 @@ final public void getCdUserDataCopy(int iSourcePos, @Nonnull byte[] abOut, int i /** Returns a copy of the underlying sector data, with raw * header/footer and everything it has. */ final public @Nonnull byte[] getRawSectorDataCopy() { - return Misc.copyOfRange(_abSectorBytes, - _iByteStartOffset, - _iByteStartOffset+getRawCdSectorSize()); + return Arrays.copyOfRange(_abSectorBytes, + _iByteStartOffset, + _iByteStartOffset+getRawCdSectorSize()); } /** Returns an InputStream of the 'user data' portion of the sector. */ diff --git a/jpsxdec/src/jpsxdec/cdreaders/CdSectorHeader.java b/jpsxdec/src/jpsxdec/cdreaders/CdSectorHeader.java index c170838..ca5da62 100644 --- a/jpsxdec/src/jpsxdec/cdreaders/CdSectorHeader.java +++ b/jpsxdec/src/jpsxdec/cdreaders/CdSectorHeader.java @@ -166,6 +166,7 @@ private static int binaryCodedDecimalToInt(int i) { return ((i >> 4) & 0xf)*10 + (i & 0xf); } + @Override public String toString() { if (_eType == Type.CD_AUDIO) return "CD audio sector"; diff --git a/jpsxdec/src/jpsxdec/cdreaders/CdSectorXaSubHeader.java b/jpsxdec/src/jpsxdec/cdreaders/CdSectorXaSubHeader.java index fd0808e..5e0a5ed 100644 --- a/jpsxdec/src/jpsxdec/cdreaders/CdSectorXaSubHeader.java +++ b/jpsxdec/src/jpsxdec/cdreaders/CdSectorXaSubHeader.java @@ -173,11 +173,6 @@ public int getChannel() { private final int _iConfidenceBalance; - private static boolean isFileValid(int iFileNumber) { - return iFileNumber >= 0 && iFileNumber <= 2 ; - } - - public CdSectorXaSubHeader(int iSector, @Nonnull byte[] abSectorData, int iStartOffset) { _iFileNum1 = abSectorData[iStartOffset+0] & 0xff; @@ -191,14 +186,12 @@ public CdSectorXaSubHeader(int iSector, @Nonnull byte[] abSectorData, int iStart int iConfidenceBalance = 0; - boolean blnValid1 = isFileValid(_iFileNum1); - if (_iFileNum1 == _iFileNum2) { - _eFileIssue = blnValid1 ? IssueType.EQUAL_BOTH_GOOD : - IssueType.EQUAL_BOTHBAD; - } else { - _eFileIssue = IssueType.diffIssue(blnValid1, isFileValid(_iFileNum2)); - iConfidenceBalance += _eFileIssue.Balance; - } + // I've tried to put some constraint on what is considered a + // 'good' file number, but PSX seems to allow for any byte value. + if (_iFileNum1 == _iFileNum2) + _eFileIssue = IssueType.EQUAL_BOTH_GOOD; + else + _eFileIssue = IssueType.DIFF_BOTHGOOD; // my understanding is channel is technically supposed to be // between 0 and 31, but PSX seems to allow for any byte value. @@ -207,7 +200,7 @@ public CdSectorXaSubHeader(int iSector, @Nonnull byte[] abSectorData, int iStart else _eChannelIssue = IssueType.DIFF_BOTHGOOD; - blnValid1 = _submode1.isValid(); + boolean blnValid1 = _submode1.isValid(); if (_submode1.toByte() == _submode2.toByte()) { _eSubModeIssue = blnValid1 ? IssueType.EQUAL_BOTH_GOOD : IssueType.EQUAL_BOTHBAD; diff --git a/jpsxdec/src/jpsxdec/cdreaders/DiscPatcher.java b/jpsxdec/src/jpsxdec/cdreaders/DiscPatcher.java index f6d82d6..a03f497 100644 --- a/jpsxdec/src/jpsxdec/cdreaders/DiscPatcher.java +++ b/jpsxdec/src/jpsxdec/cdreaders/DiscPatcher.java @@ -49,7 +49,6 @@ import jpsxdec.i18n.UnlocalizedMessage; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.util.IO; -import jpsxdec.util.IOException6; import jpsxdec.util.Misc; import jpsxdec.util.TaskCanceledException; @@ -64,7 +63,7 @@ public class DiscPatcher { private static final Logger LOG = Logger.getLogger(DiscPatcher.class.getName()); - public static class CreatePatchFileException extends IOException6 { + public static class CreatePatchFileException extends IOException { @Nonnull private final File _file; @@ -79,7 +78,7 @@ public File getFile() { } - public static class WritePatchException extends IOException6 { + public static class WritePatchException extends IOException { @Nonnull private final File _file; @@ -93,7 +92,7 @@ public File getFile() { } } - public static class PatchReadException extends IOException6 { + public static class PatchReadException extends IOException { @Nonnull private final File _file; @@ -107,7 +106,7 @@ public File getFile() { } } - public static class ApplyPatchToCdException extends IOException6 { + public static class ApplyPatchToCdException extends IOException { @Nonnull private final File _file; @@ -160,6 +159,15 @@ public int compareTo(PatchEntry o) { } } + @Override + public boolean equals(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException(); + } } @Nonnull diff --git a/jpsxdec/src/jpsxdec/cmdline/CommandLine.java b/jpsxdec/src/jpsxdec/cmdline/CommandLine.java index 9da84fd..40596b4 100644 --- a/jpsxdec/src/jpsxdec/cmdline/CommandLine.java +++ b/jpsxdec/src/jpsxdec/cmdline/CommandLine.java @@ -151,9 +151,9 @@ private static void checkVerbosity(@Nonnull ArgParser ap, if (iValue >= FeedbackStream.NONE && iValue <= FeedbackStream.MORE) fbs.setLevel(iValue); else - fbs.printlnWarn(I.CMD_VERBOSE_LVL_INVALID_NUM(iValue)); + fbs.printlnWarn(I.CMD_INVALID_VALUE_FOR_CMD(verbose.value, "-v,-verbose")); } catch (NumberFormatException ex) { - fbs.printlnWarn(I.CMD_VERBOSE_LVL_INVALID_STR(verbose.value)); + fbs.printlnWarn(I.CMD_INVALID_VALUE_FOR_CMD(verbose.value, "-v,-verbose")); } } } diff --git a/jpsxdec/src/jpsxdec/cmdline/Command_CopySect.java b/jpsxdec/src/jpsxdec/cmdline/Command_CopySect.java index 58198da..4531da2 100644 --- a/jpsxdec/src/jpsxdec/cmdline/Command_CopySect.java +++ b/jpsxdec/src/jpsxdec/cmdline/Command_CopySect.java @@ -70,7 +70,7 @@ public Command_CopySect() { protected @CheckForNull ILocalizedMessage validate(@Nonnull String s) { _aiStartEndSectors = parseNumberRange(s); if (_aiStartEndSectors == null) { - return I.CMD_SECTOR_RANGE_INVALID(s); + return I.CMD_INVALID_VALUE_FOR_CMD(s, "-copysect"); } else { return null; } diff --git a/jpsxdec/src/jpsxdec/cmdline/Command_Items.java b/jpsxdec/src/jpsxdec/cmdline/Command_Items.java index 6878beb..e030486 100644 --- a/jpsxdec/src/jpsxdec/cmdline/Command_Items.java +++ b/jpsxdec/src/jpsxdec/cmdline/Command_Items.java @@ -80,12 +80,12 @@ public Command_Item() { try { _iItemNum = Integer.parseInt(s); if (_iItemNum < 0) - return I.CMD_ITEM_NUMBER_INVALID(s); + return I.CMD_INVALID_VALUE_FOR_CMD(s, "-i,-item"); else return null; } catch (NumberFormatException ex) { if (s.contains(" ")) - return I.CMD_ITEM_ID_INVALID(s); + return I.CMD_INVALID_VALUE_FOR_CMD(s, "-i,-item"); _sItemId = s; return null; } @@ -286,7 +286,7 @@ private static void decodeDiscItem(@Nonnull DiscItem item, @CheckForNull File di fbs.println(); - builder.printSelectedOptions(fbs); + builder.printSelectedOptions(fbs.makeLogger()); long lngStart, lngEnd; lngStart = System.currentTimeMillis(); diff --git a/jpsxdec/src/jpsxdec/cmdline/Command_SectorDump.java b/jpsxdec/src/jpsxdec/cmdline/Command_SectorDump.java index 863de9d..2da990f 100644 --- a/jpsxdec/src/jpsxdec/cmdline/Command_SectorDump.java +++ b/jpsxdec/src/jpsxdec/cmdline/Command_SectorDump.java @@ -45,8 +45,7 @@ import jpsxdec.cdreaders.CdFileSectorReader; import jpsxdec.i18n.I; import jpsxdec.i18n.ILocalizedMessage; -import jpsxdec.i18n.log.ILocalizedLogger; -import jpsxdec.i18n.log.ShouldNotLog; +import jpsxdec.i18n.log.DebugLogger; import jpsxdec.modules.IIdentifiedSector; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.ArgParser; @@ -82,9 +81,8 @@ public void execute(@Nonnull ArgParser ap) throws CommandLineException { } SectorCounter counter = new SectorCounter(); SectorClaimSystem it = SectorClaimSystem.create(cdReader); - ILocalizedLogger log = new ShouldNotLog(); while (it.hasNext()) { - SectorClaimSystem.ClaimedSector cs = it.next(log); + SectorClaimSystem.ClaimedSector cs = it.next(DebugLogger.Log); IIdentifiedSector idSect = cs.getClaimer(); if (idSect != null) ps.println(idSect); diff --git a/jpsxdec/src/jpsxdec/cmdline/Command_Static.java b/jpsxdec/src/jpsxdec/cmdline/Command_Static.java index 6906af9..3183777 100644 --- a/jpsxdec/src/jpsxdec/cmdline/Command_Static.java +++ b/jpsxdec/src/jpsxdec/cmdline/Command_Static.java @@ -44,22 +44,29 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.imageio.ImageIO; +import jpsxdec.i18n.FeedbackStream; import jpsxdec.i18n.I; import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.i18n.UnlocalizedMessage; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.UserFriendlyLogger; +import jpsxdec.modules.video.save.AutowireVDP; import jpsxdec.modules.video.save.MdecDecodeQuality; import jpsxdec.modules.video.save.VDP; import jpsxdec.modules.video.save.VideoFileNameFormatter; import jpsxdec.modules.video.save.VideoFormat; -import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; +import jpsxdec.psxvideo.bitstreams.BitStreamDebugging; +import jpsxdec.psxvideo.mdec.ChromaUpsample; import jpsxdec.psxvideo.mdec.MdecDecoder; -import jpsxdec.psxvideo.mdec.MdecDecoder_double_interpolate; +import jpsxdec.psxvideo.mdec.MdecDecoder_double; import jpsxdec.psxvideo.mdec.MdecInputStreamReader; import jpsxdec.tim.Tim; import jpsxdec.util.ArgParser; @@ -88,7 +95,7 @@ private static enum StaticType { return null; } } - return I.CMD_STATIC_TYPE_INVALID(s); + return I.CMD_INVALID_VALUE_FOR_CMD(s, "-static"); } public void execute(@Nonnull ArgParser ap) throws CommandLineException { @@ -99,21 +106,45 @@ public void execute(@Nonnull ArgParser ap) throws CommandLineException { BooleanHolder debug = ap.addBoolOption("-debug"); StringHolder dimentions = ap.addStringOption("-dim"); StringHolder quality = ap.addStringOption("-quality","-q"); - StringHolder format = ap.addStringOption("-fmt"); + StringHolder outFormat = ap.addStringOption("-fmt"); StringHolder upsample = ap.addStringOption("-up"); //...... ap.match(); //...... + + // verify dimensions if (dimentions.value == null) { throw new CommandLineException(I.CMD_DIM_OPTION_REQURIED()); } final int iWidth; final int iHeight; int[] aiDim = Misc.splitInt(dimentions.value, "x"); + if (aiDim == null || aiDim.length != 2) + throw new CommandLineException(I.CMD_INVALID_VALUE_FOR_CMD(dimentions.value, "-dim")); iWidth = aiDim[0]; iHeight = aiDim[1]; + + // valid output formats + List validOutputFormat = new ArrayList(Arrays.asList( + VideoFormat.IMGSEQ_BITSTREAM, VideoFormat.IMGSEQ_JPG, VideoFormat.IMGSEQ_BMP, VideoFormat.IMGSEQ_PNG + )); + if (_eStaticType == StaticType.bs) + validOutputFormat.add(VideoFormat.IMGSEQ_MDEC); + + // verify output format + VideoFormat outputFormat; + _fbs.println(I.CMD_READING_STATIC_FILE(inFile)); + if (outFormat.value == null) { + outputFormat = VideoFormat.IMGSEQ_PNG; + } else { + outputFormat = VideoFormat.fromCmdLine(outFormat.value); + if (!validOutputFormat.contains(outputFormat)) + throw new CommandLineException(I.CMD_INVALID_VALUE_FOR_CMD(outFormat.value, "-fmt")); + } + + // enable debugging if (debug.value) { - BitStreamUncompressor.DEBUG = true; + BitStreamDebugging.DEBUG = true; MdecDecoder.DEBUG = true; boolean blnAssertsEnabled = false; assert blnAssertsEnabled = true; @@ -122,96 +153,156 @@ public void execute(@Nonnull ArgParser ap) throws CommandLineException { _fbs.printlnWarn(I.CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA()); } } - _fbs.println(I.CMD_READING_STATIC_FILE(inFile)); - String sFileBaseName = Misc.removeExt(inFile.getName()); - VideoFormat vf = VideoFormat.IMGSEQ_PNG; - if (format.value != null) { - vf = VideoFormat.fromCmdLine(format.value); - if (vf == null || vf.isAvi() || vf == VideoFormat.IMGSEQ_BITSTREAM) - throw new CommandLineException(I.CMD_FORMAT_INVALID(format.value)); - } - VDP.IMdecListener mdecOut; - VideoFileNameFormatter formatter = new VideoFileNameFormatter(null, sFileBaseName, vf, iWidth, iHeight); + + // make logger UserFriendlyLogger log = new UserFriendlyLogger("static", _fbs.getUnderlyingStream()); - UserFriendlyLogger.WarnErrCounter warnErrCount = new UserFriendlyLogger.WarnErrCounter(); - log.setListener(warnErrCount); - if (vf == VideoFormat.IMGSEQ_MDEC) { - mdecOut = new VDP.Mdec2File(formatter, iWidth, iHeight, log); - } else if (vf == VideoFormat.IMGSEQ_JPG) { - VDP.Mdec2Jpeg m2j = new VDP.Mdec2Jpeg(formatter, iWidth, iHeight, log); - mdecOut = m2j; - } else { - MdecDecodeQuality decQuality = MdecDecodeQuality.HIGH_PLUS; - if (quality.value != null) { - decQuality = MdecDecodeQuality.fromCmdLine(quality.value); - if (decQuality == null) - throw new CommandLineException(I.CMD_QUALITY_INVALID(quality.value)); - } - MdecDecoder vidDecoder = decQuality.makeDecoder(iWidth, iHeight); - _fbs.println(I.CMD_USING_QUALITY(decQuality.getCmdLine())); - if (vidDecoder instanceof MdecDecoder_double_interpolate) { - MdecDecoder_double_interpolate.Upsampler up = - MdecDecoder_double_interpolate.Upsampler.Bicubic; - if (upsample.value != null) { - up = MdecDecoder_double_interpolate.Upsampler.fromCmdLine(upsample.value); - if (up == null) - throw new CommandLineException(I.CMD_UPSAMPLING_INVALID(upsample.value)); - } - _fbs.println(I.CMD_USING_UPSAMPLING(up.getDescription())); - ((MdecDecoder_double_interpolate) vidDecoder).setResampler(up); - } - VDP.Mdec2Decoded m2d = new VDP.Mdec2Decoded(vidDecoder, log); - mdecOut = m2d; - VDP.Decoded2JavaImage imgOut = new VDP.Decoded2JavaImage(formatter, vf.getImgFmt(), iWidth, iHeight, log); - m2d.setDecoded(imgOut); + FileAndIssueListener fileAndIssueListener = new FileAndIssueListener(_fbs); + log.setListener(fileAndIssueListener); + + // file listener + String sFileBaseName = Misc.removeExt(inFile.getName()); + + // build pipeline + AutowireVDP pipeline = setupPipeline(iWidth, iHeight, outputFormat, sFileBaseName, quality.value, upsample.value, log); + + // read input file + byte[] abFileData; + try { + abFileData = IO.readFile(inFile); + } catch (FileNotFoundException ex) { + throw new CommandLineException(I.IO_OPENING_FILE_NOT_FOUND_NAME(inFile.toString()), ex); + } catch (IOException ex) { + throw new CommandLineException(I.IO_READING_FILE_ERROR_NAME(inFile.toString()), ex); } - _fbs.println(I.CMD_SAVING_AS(formatter.format(null, log))); + + Fraction dummyFrameNumber = new Fraction(-1); try { - byte[] abBitstream = IO.readFile(inFile); // TODO: separate exception for reading here if (_eStaticType == StaticType.bs) { - VDP.Bitstream2Mdec b2m = new VDP.Bitstream2Mdec(mdecOut); - b2m.bitstream(abBitstream, abBitstream.length, null, new Fraction(-1)); + pipeline.setMap(new VDP.Bitstream2Mdec()); + pipeline.autowire(); + // finally convert the file + pipeline.getBitstreamListener().bitstream(abFileData, abFileData.length, null, dummyFrameNumber); } else { - mdecOut.mdec(new MdecInputStreamReader(abBitstream), null, new Fraction(-1)); + pipeline.autowire(); + // finally convert the file + pipeline.getMdecListener().mdec(new MdecInputStreamReader(abFileData), null, dummyFrameNumber); } - if (warnErrCount.getWarnCount() == 0 && warnErrCount.getErrCount() == 0) - _fbs.println(I.CMD_FRAME_CONVERT_OK()); // TODO: have another message saying complete with issues - // TODO: how do I know the operation even generated any output? - } catch (FileNotFoundException ex) { - throw new CommandLineException(I.IO_OPENING_FILE_NOT_FOUND_NAME(inFile.toString()), ex); - } catch (IOException ex) { - throw new CommandLineException(I.IO_WRITING_TO_FILE_ERROR_NAME(inFile.toString()), ex); + + if (!fileAndIssueListener.blnHadIssue) { + _fbs.println(I.CMD_FRAME_CONVERT_OK()); + } + _fbs.println(I.CMD_NUM_FILES_CREATED(fileAndIssueListener.genFiles.size())); + } catch (LoggedFailure ex) { _fbs.printErr(ex.getSourceMessage()); } return; case tim: - _fbs.println(I.CMD_READING_TIM(inFile)); - FileInputStream is = null; - try { - is = new FileInputStream(inFile); - String sOutBaseName = Misc.removeExt(inFile.getName()); - Tim tim = Tim.read(is); - _fbs.println(new UnlocalizedMessage(tim.toString())); - int iDigitCount = String.valueOf(tim.getPaletteCount()).length(); - for (int i = 0; i < tim.getPaletteCount(); i++) { - BufferedImage bi = tim.toBufferedImage(i); - String sFileName = String.format("%s_p%0" + iDigitCount + "d.png", sOutBaseName, i); - File file = new File(sFileName); - _fbs.println(I.IO_WRITING_FILE(file.getName())); - ImageIO.write(bi, "png", file); - } - _fbs.println(I.CMD_IMAGE_CONVERT_OK()); - } catch (BinaryDataNotRecognized ex) { - throw new CommandLineException(I.CMD_NOT_TIM(), ex); - } catch (IOException ex) { - throw new CommandLineException(I.CMD_TIM_IO_ERR(), ex); - } finally { - IO.closeSilently(is, Logger.getLogger(Command_Static.class.getName())); - } + saveTim(inFile); return; } throw new RuntimeException("Shouldn't happen"); } + private static class FileAndIssueListener implements VDP.GeneratedFileListener, UserFriendlyLogger.OnWarnErr { + public final List genFiles = new ArrayList(); + public final FeedbackStream fbs; + public boolean blnHadIssue = false; + public FileAndIssueListener(FeedbackStream fbs) { + this.fbs = fbs; + } + public void fileGenerated(File f) { + genFiles.add(f); + } + public void onWarn(ILocalizedMessage msg) { + fbs.printlnWarn(msg); + blnHadIssue = true; + } + public void onErr(ILocalizedMessage msg) { + fbs.printlnErr(msg); + blnHadIssue = true; + } + } + + private AutowireVDP setupPipeline( + int iWidth, int iHeight, + @Nonnull VideoFormat outputFormat, @Nonnull String sOutputBaseName, + @CheckForNull String sOutputQuality, @CheckForNull String sUpsampleQuality, + @Nonnull ILocalizedLogger log) + throws CommandLineException + { + VideoFileNameFormatter formatter = new VideoFileNameFormatter(null, sOutputBaseName, outputFormat, iWidth, iHeight); + AutowireVDP pipline = new AutowireVDP(); + + switch (outputFormat) { + case IMGSEQ_MDEC: + pipline.setMap(new VDP.Mdec2File(formatter, iWidth, iHeight, log)); + break; + case IMGSEQ_JPG: + pipline.setMap(new VDP.Mdec2Jpeg(formatter, iWidth, iHeight, log)); + break; + + case IMGSEQ_BMP: + case IMGSEQ_PNG: + // made decoder, verify decoding quality + MdecDecodeQuality decQuality = MdecDecodeQuality.HIGH; + if (sOutputQuality != null) { + decQuality = MdecDecodeQuality.fromCmdLine(sOutputQuality); + if (decQuality == null) + throw new CommandLineException(I.CMD_INVALID_VALUE_FOR_CMD(sOutputQuality, "-q,-quality")); + } + MdecDecoder vidDecoder = decQuality.makeDecoder(iWidth, iHeight); + _fbs.println(I.CMD_USING_QUALITY(decQuality.getCmdLine())); + + // verify upsample quality + ChromaUpsample up = ChromaUpsample.Bicubic; + if (vidDecoder instanceof MdecDecoder_double) { + if (sUpsampleQuality != null) { + up = ChromaUpsample.fromCmdLine(sUpsampleQuality); + if (up == null) + throw new CommandLineException(I.CMD_INVALID_VALUE_FOR_CMD(sUpsampleQuality, "-up")); + } + _fbs.println(I.CMD_UPSAMPLE_QUALITY(up.getDescription().getLocalizedMessage())); + ((MdecDecoder_double) vidDecoder).setUpsampler(up); + } + + pipline.setMap(new VDP.Mdec2Decoded(vidDecoder, log)); + + pipline.setMap(new VDP.Decoded2JavaImage(formatter, outputFormat.getImgFmt(), iWidth, iHeight, log)); + break; + default: + throw new RuntimeException(); + } + + _fbs.println(I.CMD_SAVING_AS(formatter.format(null, log))); + + return pipline; + } + + private void saveTim(@Nonnull File inFile) throws CommandLineException { + _fbs.println(I.CMD_READING_TIM(inFile)); + FileInputStream is = null; + try { + is = new FileInputStream(inFile); + String sOutBaseName = Misc.removeExt(inFile.getName()); + Tim tim = Tim.read(is); + _fbs.println(new UnlocalizedMessage(tim.toString())); + int iDigitCount = String.valueOf(tim.getPaletteCount()).length(); + for (int i = 0; i < tim.getPaletteCount(); i++) { + BufferedImage bi = tim.toBufferedImage(i); + String sFileName = String.format("%s_p%0" + iDigitCount + "d.png", sOutBaseName, i); + File file = new File(sFileName); + _fbs.println(I.IO_WRITING_FILE(file.getName())); + ImageIO.write(bi, "png", file); + } + _fbs.println(I.CMD_IMAGE_CONVERT_OK()); + } catch (BinaryDataNotRecognized ex) { + throw new CommandLineException(I.CMD_NOT_TIM(), ex); + } catch (IOException ex) { + throw new CommandLineException(I.CMD_TIM_IO_ERR(), ex); + } finally { + IO.closeSilently(is, Logger.getLogger(Command_Static.class.getName())); + } + } + } diff --git a/jpsxdec/src/jpsxdec/discitems/CombinedBuilderListener.java b/jpsxdec/src/jpsxdec/discitems/CombinedBuilderListener.java index 71f6ff2..d4a1830 100644 --- a/jpsxdec/src/jpsxdec/discitems/CombinedBuilderListener.java +++ b/jpsxdec/src/jpsxdec/discitems/CombinedBuilderListener.java @@ -79,6 +79,7 @@ public void addListeners(ChangeListener ... aoControls) { * All listening controls will be notified of the change. * @return if the {@link DiscItemSaverBuilder} is compatible. */ + @SuppressWarnings("unchecked") public boolean changeSourceBuilder(@Nonnull DiscItemSaverBuilder saverBuilder) { if (_cls.equals(saverBuilder.getClass())) { T oldBuilder = _saverBuilder; diff --git a/jpsxdec/src/jpsxdec/discitems/DiscItemSaverBuilder.java b/jpsxdec/src/jpsxdec/discitems/DiscItemSaverBuilder.java index 6153e87..3d7d4bd 100644 --- a/jpsxdec/src/jpsxdec/discitems/DiscItemSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/discitems/DiscItemSaverBuilder.java @@ -48,6 +48,7 @@ import jpsxdec.i18n.FeedbackStream; import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.util.ArgParser; import jpsxdec.util.TaskCanceledException; @@ -123,7 +124,7 @@ final protected void addGeneratedFile(@Nonnull File file) { /** Configure the saver builder from command-line arguments. */ abstract public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fbs); /** Prints the options used for saving. */ - abstract public void printSelectedOptions(@Nonnull FeedbackStream fbs); + abstract public void printSelectedOptions(@Nonnull ILocalizedLogger log); /** Get a localized summary of what files will be generated on save. */ abstract public @Nonnull ILocalizedMessage getOutputSummary(); diff --git a/jpsxdec/src/jpsxdec/discitems/ParagraphPanel.java b/jpsxdec/src/jpsxdec/discitems/ParagraphPanel.java index 0b3d0ad..067ed95 100644 --- a/jpsxdec/src/jpsxdec/discitems/ParagraphPanel.java +++ b/jpsxdec/src/jpsxdec/discitems/ParagraphPanel.java @@ -87,10 +87,14 @@ public void stateChanged(ChangeEvent e) { fireStateChanged(); if (__label.isEnabled() != isEnabled()) __label.setEnabled(isEnabled()); + boolean v = isVisibile(); + __label.setVisible(v); + __chk.setVisible(v); } @Override abstract public boolean isSelected(); @Override abstract public void setSelected(boolean b); @Override abstract public boolean isEnabled(); + public boolean isVisibile() { return true; } } diff --git a/jpsxdec/src/jpsxdec/discitems/SerializedDiscItem.java b/jpsxdec/src/jpsxdec/discitems/SerializedDiscItem.java index 32f4b51..1318515 100644 --- a/jpsxdec/src/jpsxdec/discitems/SerializedDiscItem.java +++ b/jpsxdec/src/jpsxdec/discitems/SerializedDiscItem.java @@ -237,6 +237,10 @@ private void checkValidKey(String sKey) { // ========================================================================= // Reading fields + public boolean hasField(@Nonnull String sFieldName) { + return _fields.containsKey(sFieldName); + } + public @Nonnull String getString(@Nonnull String sFieldName) throws LocalizedDeserializationFail { String sValue = _fields.get(sFieldName); if (sValue == null) throw new LocalizedDeserializationFail(I.SERIALIZATION_FIELD_NOT_FOUND(sFieldName)); @@ -260,7 +264,7 @@ public long getLong(@Nonnull String sFieldName) throws LocalizedDeserializationF try { return Long.parseLong(sValue); } catch (NumberFormatException e) { - throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_LONG(sValue)); + throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER(sValue)); } } @@ -270,7 +274,7 @@ public int getInt(@Nonnull String sFieldName) throws LocalizedDeserializationFai try { return Integer.parseInt(sValue); } catch (NumberFormatException e) { - throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_INT(sValue)); + throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER(sValue)); } } @@ -282,7 +286,7 @@ public int getInt(@Nonnull String sFieldName, int iDefault) throws LocalizedDese try { return Integer.parseInt(sValue); } catch (NumberFormatException e) { - throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_INT(sValue)); + throw new LocalizedDeserializationFail(I.SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER(sValue)); } } diff --git a/jpsxdec/src/jpsxdec/formats/RGB.java b/jpsxdec/src/jpsxdec/formats/RGB.java index d90a389..f2ac3b5 100644 --- a/jpsxdec/src/jpsxdec/formats/RGB.java +++ b/jpsxdec/src/jpsxdec/formats/RGB.java @@ -79,6 +79,7 @@ public int toInt() { return 0xFF000000 | clampr | clampg | clampb; } + @Override public String toString() { return String.format("(%d, %d, %d)", r, g, b); } diff --git a/jpsxdec/src/jpsxdec/formats/Rec601YCbCr.java b/jpsxdec/src/jpsxdec/formats/Rec601YCbCr.java index 5a76e8b..ab3b051 100644 --- a/jpsxdec/src/jpsxdec/formats/Rec601YCbCr.java +++ b/jpsxdec/src/jpsxdec/formats/Rec601YCbCr.java @@ -116,6 +116,7 @@ public void toRgb(@Nonnull RGB rgb1, @Nonnull RGB rgb2, @Nonnull RGB rgb3, @Nonn rgb4.setB(dblYshift + dblChromBlue ); } + @Override public String toString() { return String.format("([%f, %f, %f, %f] %f, %f)", y1, y2, y3, y4, cb, cr); } diff --git a/jpsxdec/src/jpsxdec/gui/Gui.java b/jpsxdec/src/jpsxdec/gui/Gui.java index 7486f9c..421e59c 100644 --- a/jpsxdec/src/jpsxdec/gui/Gui.java +++ b/jpsxdec/src/jpsxdec/gui/Gui.java @@ -48,14 +48,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import javax.sound.sampled.LineUnavailableException; import javax.swing.*; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -77,6 +75,7 @@ import jpsxdec.util.Misc; import jpsxdec.util.player.PlayController; import jpsxdec.util.player.PlayController.Event; +import jpsxdec.util.player.PlayerException; import org.openide.awt.DropDownButtonFactory; public class Gui extends javax.swing.JFrame { @@ -86,22 +85,21 @@ public class Gui extends javax.swing.JFrame { // ------------------------------------------------------------------------- @CheckForNull - private DiscIndex _index; + private transient DiscIndex _index; @CheckForNull - private PlayController _currentPlayer; + private transient PlayController _currentPlayer; private final ArrayList _saverGuis = new ArrayList(); @Nonnull - private final GuiSettings _settings; + private final transient GuiSettings _settings; @CheckForNull private String _sCommandLineFile; - private boolean _blnIsPaused = true; // ------------------------------------------------------------------------- public Gui() { - // use the system's L&F if available (for great justice!) + // use the system's L&F if available try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); SwingUtilities.updateComponentTreeUI(this); @@ -125,23 +123,10 @@ public Gui() { convertToolbar(); - Image icon16 = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon16.png")); - try { - // setIconImages() is only available in Java 6+, but jPSXdec is targetted for Java 5 - // we optionally take advantage of it using reflection - Method setIconImages = this.getClass().getMethod("setIconImages", List.class); - ArrayList icons = new ArrayList(3); - Image icon32 = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon32.png")); - Image icon48 = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon48.png")); - icons.add(icon16); - icons.add(icon32); - icons.add(icon48); - setIconImages.invoke(this, icons); - } catch (Exception ex) { - LOG.log(Level.INFO, "Unable to set multiple icons", ex); - setIconImage(icon16); - } - + Image icon16 = Toolkit.getDefaultToolkit().createImage(Gui.class.getResource("icon16.png")); + Image icon32 = Toolkit.getDefaultToolkit().createImage(Gui.class.getResource("icon32.png")); + Image icon48 = Toolkit.getDefaultToolkit().createImage(Gui.class.getResource("icon48.png")); + setIconImages(Arrays.asList(icon16, icon32, icon48)); } @@ -403,7 +388,7 @@ private void openDisc(@Nonnull File file) { cd.close(); // expose close exception } else { if (generatedIndex.size() == 0) { - JOptionPane.showMessageDialog(this, "Could not identify anything in " + file.toString()); // I18N + JOptionPane.showMessageDialog(this, I.GUI_DIALOG_COULD_NOT_IDENTIFY_ANYTHING(file.toString()).getLocalizedMessage()); } else { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setIndex(generatedIndex, null); @@ -750,14 +735,10 @@ private void _guiDiscTreeValueChanged(javax.swing.event.TreeSelectionEvent evt) private void _guiPlayPauseBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event__guiPlayPauseBtnActionPerformed try { if (_currentPlayer != null) { - if (_blnIsPaused) { + if (_currentPlayer.isPaused()) { _currentPlayer.unpause(); - _guiPlayPauseBtn.setText(I.GUI_PAUSE_BTN().getLocalizedMessage()); - _blnIsPaused = false; } else { _currentPlayer.pause(); - _guiPlayPauseBtn.setText(I.GUI_PLAY_BTN().getLocalizedMessage()); - _blnIsPaused = true; } } } catch (Throwable ex) { @@ -890,12 +871,22 @@ private void _guiChooseDirActionPerformed(java.awt.event.ActionEvent evt) {//GEN private transient final PlayController.PlayerListener _playerListener = new PlayController.PlayerListener() { - public void update(@Nonnull Event eEvent) { - switch (eEvent) { - case Stop: - _guiPlayPauseBtn.setEnabled(false); - break; - } + public void event(@Nonnull final Event eEvent) { + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + switch (eEvent) { + case End: + _guiPlayPauseBtn.setEnabled(false); + break; + case Pause: + _guiPlayPauseBtn.setText(I.GUI_PLAY_BTN().getLocalizedMessage()); + break; + case Play: + _guiPlayPauseBtn.setText(I.GUI_PAUSE_BTN().getLocalizedMessage()); + break; + } + } + }); } }; @@ -908,8 +899,8 @@ private void _guiTabStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST _currentPlayer = selection.getPlayer(); if (_currentPlayer != null) { try { - _currentPlayer.start(); - } catch (LineUnavailableException ex) { + _currentPlayer.activate(); + } catch (PlayerException ex) { LOG.log(Level.SEVERE, null, ex); _currentPlayer = null; } @@ -919,14 +910,13 @@ private void _guiTabStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST _guiPreviewPanel.add(_currentPlayer.getVideoScreen()); _guiPreviewPanel.revalidate(); } - _currentPlayer.addLineListener(_playerListener); + _currentPlayer.addEventListener(_playerListener); _guiPlayPauseBtn.setText(I.GUI_PLAY_BTN().getLocalizedMessage()); _guiPlayPauseBtn.setEnabled(true); - _blnIsPaused = true; } } else { if (_currentPlayer != null) { - _currentPlayer.stop(); + _currentPlayer.terminate(); _currentPlayer = null; _guiPreviewPanel.removeAll(); _guiPreviewPanel.validate(); @@ -985,7 +975,7 @@ private void formWindowOpened(java.awt.event.WindowEvent evt) {//GEN-FIRST:event byte[] ab = new byte[iCount]; int iSize = IO.readByteArrayMax(is, ab, 0, ab.length); if (iSize < ab.length) - return Misc.copyOfRange(ab, 0, iSize); + return Arrays.copyOfRange(ab, 0, iSize); else return ab; } diff --git a/jpsxdec/src/jpsxdec/gui/GuiFileFilters.java b/jpsxdec/src/jpsxdec/gui/GuiFileFilters.java index 30af590..9d09430 100644 --- a/jpsxdec/src/jpsxdec/gui/GuiFileFilters.java +++ b/jpsxdec/src/jpsxdec/gui/GuiFileFilters.java @@ -70,10 +70,8 @@ public boolean accept(File f) { s.endsWith(".img") || s.endsWith(".mdf") || s.endsWith(".str") || - s.endsWith(".mov") || s.endsWith(".iki") || - s.endsWith(".xa") || - s.endsWith(".xai"); + s.endsWith(".xa"); } }, new FileFilter() { @@ -103,8 +101,7 @@ public boolean accept(File f) { public boolean accept(File f) { String s = f.getName().toLowerCase(); return f.isDirectory() || - s.endsWith(".xa") || - s.endsWith(".xai"); + s.endsWith(".xa"); } }, }; diff --git a/jpsxdec/src/jpsxdec/gui/GuiTree.java b/jpsxdec/src/jpsxdec/gui/GuiTree.java index 473c45b..9302288 100644 --- a/jpsxdec/src/jpsxdec/gui/GuiTree.java +++ b/jpsxdec/src/jpsxdec/gui/GuiTree.java @@ -126,20 +126,6 @@ public static Select[] getAvailableValues() { public void formatTreeTable(@Nonnull DiscIndex index) { _root = buildTree(index.getRoot()); - /* If using Netbeans Outline - setDefaultRenderer(Boolean.class, new OptionalBooleanTableCellRenderer()); - DiscTreeModel ttModel = new DiscTreeModel(_root); - OutlineModel om = DefaultOutlineModel.createOutlineModel(ttModel, ttModel); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - setFillsViewportHeight(true); - setIntercellSpacing(new Dimension(0, 0)); - setShowGrid(false); - setRootVisible(false); - setModel(om); - setRowSorter(null); - setRowHeight(getRowHeight() - 2); - / */ - FontMetrics fm = getFontMetrics(getFont()); int iSectorWidth = fm.stringWidth("999999-999999"); int iNumberWidth = fm.stringWidth(String.valueOf(index.size()) + "99"); @@ -147,7 +133,6 @@ public void formatTreeTable(@Nonnull DiscIndex index) { int iTypeWidth = fm.stringWidth(DiscItem.GeneralType.Sound.getName().toString()); setDefaultRenderer(Boolean.class, new OptionalBooleanTableCellRenderer()); - //_guiDiscTree.setDefaultRenderer(Integer.class, new CenteredIntegerTableCellRenderer()); setTreeCellRenderer(new TreeIconRenderer()); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -160,12 +145,8 @@ public void formatTreeTable(@Nonnull DiscIndex index) { colMod.getColumn(COLUMNS.Type.ordinal()).setPreferredWidth(iTypeWidth + 10); TableColumn detailsCol = colMod.getColumn(COLUMNS.Details.ordinal()); detailsCol.setPreferredWidth(Math.max(250, detailsCol.getWidth())); - // */ } - //public void expandAll() {} public void collapseAll() {} - //public void addTreeSelectionListener(TreeSelectionListener tsl) {} - public @CheckForNull TreeItem getTreeTblSelection() { return (TreeItem) getValueAt(getSelectedRow(), convertColumnIndexToView(COLUMNS.Name.ordinal())); } @@ -519,9 +500,7 @@ else if (cmd == Select.ALL_SOUND) // SPU support // ------------------------------------------------------------------------- - private static class DiscTreeModel implements TreeTableModel - //, RowModel // Outline - { + private static class DiscTreeModel implements TreeTableModel { @Nonnull private final TreeItem _treeRoot; @@ -581,16 +560,6 @@ public void setValueAt(@Nonnull Object value, @Nonnull Object node, int i) { assert i == COLUMNS.Save.ordinal(); ((DiscItemTreeItem)node).setSave(((Boolean)value).booleanValue()); } - - // for Netbeans Outline - public @CheckForNull Object getValueFor(@Nonnull Object node, int column) { - return getValueAt(node, column); - } - - // for Netbeans Outline - public void setValueFor(@Nonnull Object node, int column, @Nonnull Object value) { - setValueAt(value, node, column); - } } // ------------------------------------------------------------------------- diff --git a/jpsxdec/src/jpsxdec/i18n/FeedbackStream.java b/jpsxdec/src/jpsxdec/i18n/FeedbackStream.java index 660466b..a6070ef 100644 --- a/jpsxdec/src/jpsxdec/i18n/FeedbackStream.java +++ b/jpsxdec/src/jpsxdec/i18n/FeedbackStream.java @@ -34,16 +34,23 @@ * 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.i18n; import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.log.ILocalizedLogger; /** * Filters outputting text based on verbosity level. */ public class FeedbackStream { + private static final Logger LOG = Logger.getLogger(FeedbackStream.class.getName()); + public static final int MORE = 4; public static final int NORM = 3; public static final int WARN = 2; @@ -136,4 +143,32 @@ public int getLevel() { public @Nonnull PrintStream getUnderlyingStream() { return _ps; } + + public @Nonnull ILocalizedLogger makeLogger() { + return new FbsLogger(); + } + + private class FbsLogger implements ILocalizedLogger { + + public void log(@Nonnull Level level, @Nonnull ILocalizedMessage msg) { + log(level, msg, null); + } + + public void log(@Nonnull Level level, @Nonnull ILocalizedMessage msg, + @CheckForNull Throwable debugException) + { + LOG.log(level, msg.getEnglishMessage(), debugException); + if (level.intValue() < Level.INFO.intValue() || + level.intValue() == Level.ALL.intValue() || + level.intValue() == Level.CONFIG.intValue()) + printlnMore(msg); + else if (level.intValue() < Level.WARNING.intValue()) + println(msg); + else if (level.intValue() < Level.SEVERE.intValue()) + printlnWarn(msg); + else if (level.intValue() >= Level.SEVERE.intValue() && + level.intValue() != Level.OFF.intValue()) + printlnErr(msg); + } + } } diff --git a/jpsxdec/src/jpsxdec/i18n/I.java b/jpsxdec/src/jpsxdec/i18n/I.java index 15e8d15..1ab8cab 100644 --- a/jpsxdec/src/jpsxdec/i18n/I.java +++ b/jpsxdec/src/jpsxdec/i18n/I.java @@ -1,6 +1,7 @@ /* * jPSXdec Translations - * Copyright (c) 2015-2017 Michael Sabin, Víctor González, Sergi Medina + * Copyright (c) 2015-2019 + * Michael Sabin, Víctor González, Sergi Medina, Gianluigi "Infrid" Cusimano * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -137,10 +138,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Invalid item number: {0}
-

Trying to look-up and item by its numeric index and the number is invalid (probably negative)

-
    -
  • Command_Items.java
  • -
+

Trying to look-up an item by its numeric index and the number is invalid (probably negative)

*/ public static @Nonnull ILocalizedMessage CMD_ITEM_NUMBER_INVALID(@Nonnull String badItemNumber) { return msg("CMD_ITEM_NUMBER_INVALID", "Invalid item number: {0}", badItemNumber); @@ -151,9 +149,6 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Invalid item identifier: {0}

Trying to look-up an item by its string index identifier that is invalid (probably contains space)

-
    -
  • Command_Items.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_ITEM_ID_INVALID(@Nonnull String badItemIdentifier) { return msg("CMD_ITEM_ID_INVALID", "Invalid item identifier: {0}", badItemIdentifier); @@ -245,7 +240,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Detailed help for
-

The next line will display the item info

+

The next line printed to the console after this will be the description of the item

  • Command_Items.java
@@ -321,6 +316,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
  • Command_Items.java
  • +
  • Command_Static.java
*/ public static @Nonnull ILocalizedMessage CMD_NUM_FILES_CREATED(int fileCount) { @@ -440,6 +436,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
[{0}] {1}
+

e.g. [INFO] Some message

  • UserFriendlyLogger.java
@@ -452,6 +449,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
[{0}] {1} {2}
+

e.g. [WARN] Some message BadException

*/ public static @Nonnull ILocalizedMessage USER_LOG_MESSAGE_EXCEPTION(@Nonnull String logLevel, @Nonnull String logMessage, @Nonnull String exceptionName) { return msg("USER_LOG_MESSAGE_EXCEPTION", "[{0}] {1} {2}", logLevel, logMessage, exceptionName); @@ -461,6 +459,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
[{0}] {1} {2} : {3}
+

e.g. [WARN] Some message BadException: Something bad happened

*/ public static @Nonnull ILocalizedMessage USER_LOG_MESSAGE_EXCEPTION_MSG(@Nonnull String logLevel, @Nonnull String logMessage, @Nonnull String exceptionName, @Nonnull String exceptionMessage) { return msg("USER_LOG_MESSAGE_EXCEPTION_MSG", "[{0}] {1} {2} : {3}", logLevel, logMessage, exceptionName, exceptionMessage); @@ -470,6 +469,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
[{0}] {1}
+

e.g. [WARN] BadException

*/ public static @Nonnull ILocalizedMessage USER_LOG_EXCEPTION(@Nonnull String logLevel, @Nonnull String exceptionName) { return msg("USER_LOG_EXCEPTION", "[{0}] {1}", logLevel, exceptionName); @@ -479,6 +479,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
[{0}] {1} : {2}
+

e.g. [WARN] BadException: Something bad happened

*/ public static @Nonnull ILocalizedMessage USER_LOG_EXCEPTION_MSG(@Nonnull String logLevel, @Nonnull String exceptionName, @Nonnull String exceptionMessage) { return msg("USER_LOG_EXCEPTION_MSG", "[{0}] {1} : {2}", logLevel, exceptionName, exceptionMessage); @@ -488,9 +489,7 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Invalid sector range: {0}
-
    -
  • Command_CopySect.java
  • -
+

Sector range should be in the format "start-end"

*/ public static @Nonnull ILocalizedMessage CMD_SECTOR_RANGE_INVALID(@Nonnull String badSectorRangeString) { return msg("CMD_SECTOR_RANGE_INVALID", "Invalid sector range: {0}", badSectorRangeString); @@ -532,13 +531,19 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object return msg("CMD_DIM_OPTION_REQURIED", "-dim option required"); } + /** +
+
Invalid dimensions: {0}
+
+ */ + public static @Nonnull ILocalizedMessage CMD_INVALID_DIMENSIONS(@Nonnull String badDimensionsString) { + return msg("CMD_INVALID_DIMENSIONS", "Invalid dimensions: {0}", badDimensionsString); + } + /**
Invalid quality {0}
-
    -
  • Command_Static.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_QUALITY_INVALID(@Nonnull String badQuality) { return msg("CMD_QUALITY_INVALID", "Invalid quality {0}", badQuality); @@ -573,9 +578,6 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Using upsampling {0}

See CHROMA_UPSAMPLE_*_DESCRIPTION

-
    -
  • Command_Static.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_USING_UPSAMPLING(@Nonnull ILocalizedMessage upsampleDescription) { return msg("CMD_USING_UPSAMPLING", "Using upsampling {0}", upsampleDescription); @@ -597,9 +599,6 @@ private static ILocalizedMessage msg(String sKey, String sEnglishDefault, Object
Invalid format type {0}
-
    -
  • Command_Static.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_FORMAT_INVALID(@Nonnull String badFormat) { return msg("CMD_FORMAT_INVALID", "Invalid format type {0}", badFormat); @@ -646,9 +645,6 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Invalid static type: {0}
-

    -
  • Command_Static.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_STATIC_TYPE_INVALID(@Nonnull String badStaticTypeName) { return msg("CMD_STATIC_TYPE_INVALID", "Invalid static type: {0}", badStaticTypeName); @@ -690,18 +686,6 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA return msg("CMD_IMAGE_CONVERT_OK", "Image converted successfully"); } - /** -
-
Invalid upsampling {0}
-
-

    -
  • Command_Static.java
  • -
- */ - public static @Nonnull ILocalizedMessage CMD_UPSAMPLING_INVALID(@Nonnull String badUpsamplingName) { - return msg("CMD_UPSAMPLING_INVALID", "Invalid upsampling {0}", badUpsamplingName); - } - /**
Saving as: {0}
@@ -788,9 +772,6 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Invalid verbosity level {0}
-

    -
  • CommandLine.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_VERBOSE_LVL_INVALID_STR(@Nonnull String badVerbosityLevel) { return msg("CMD_VERBOSE_LVL_INVALID_STR", "Invalid verbosity level {0}", badVerbosityLevel); @@ -800,9 +781,6 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Invalid verbosity level {0,number,#}
-

    -
  • CommandLine.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_VERBOSE_LVL_INVALID_NUM(int badVerbosityNumber) { return msg("CMD_VERBOSE_LVL_INVALID_NUM", "Invalid verbosity level {0,number,#}", badVerbosityNumber); @@ -1004,6 +982,18 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA return msg("GUI_DISC_NO_RAW_HEADERS_WARNING", "Disc image does not have raw headers -- audio may not be detected."); } + /** +
+
Could not identify anything in file {0}
+
+

    +
  • Gui.java
  • +
+ */ + public static @Nonnull ILocalizedMessage GUI_DIALOG_COULD_NOT_IDENTIFY_ANYTHING(@Nonnull String fileName) { + return msg("GUI_DIALOG_COULD_NOT_IDENTIFY_ANYTHING", "Could not identify anything in file {0}", fileName); + } + /**
Select disc image or media file
@@ -1164,6 +1154,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
...
+

Button

  • Gui.java
@@ -1176,6 +1167,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Select ...
+

Button

  • Gui.java
@@ -1188,6 +1180,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Collapse All
+

Button

  • Gui.java
@@ -1200,6 +1193,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Expand All
+

Button

  • Gui.java
@@ -1212,6 +1206,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Save All Selected
+

Button

  • Gui.java
@@ -1249,6 +1244,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Applied settings to {0,number,#} items.
+

Dialog box

  • Gui.java
@@ -1261,6 +1257,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
The index has not been saved. Save index?
+

Dialog box

  • Gui.java
@@ -1273,6 +1270,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Save index?
+

Dialog box

  • Gui.java
@@ -1311,6 +1309,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
Pause
+

Button

  • Gui.java
@@ -1348,6 +1347,7 @@

Right after this string, the next string (CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA
CD images (*.iso, *.bin, *.img, *.mdf)
+

File dialog format

  • GuiFileFilters.java
@@ -1360,6 +1360,7 @@
CD images (*.iso, *.bin, *.img, *.mdf)
Index files (*.idx)
+

File dialog format

  • GuiFileFilters.java
@@ -1372,6 +1373,7 @@
Index files (*.idx)
PlayStation video (*.str, *.mov, *.iki, *.ik2)
+

File dialog format

  • GuiFileFilters.java
@@ -1384,6 +1386,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
All compatible types
+

File dialog format

  • GuiFileFilters.java
@@ -1396,6 +1399,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
PlayStation/CD-i audio (*.xa, *.xai)
+

File dialog format

  • GuiFileFilters.java
@@ -1409,6 +1413,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
The file "{0}" already exists!
 Do you want to replace it?
+

Dialog

  • BetterFileChooser.java
@@ -1470,6 +1475,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
all Videos
+

Drop-down

  • GuiTree.java
@@ -1482,6 +1488,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
all Files
+

Drop-down

  • GuiTree.java
@@ -1494,6 +1501,7 @@
PlayStation video (*.str, *.mov, *.iki, *.ik2)
all Audio (excluding video audio)
+

Drop-down

  • GuiTree.java
@@ -1506,6 +1514,7 @@
all Audio (excluding video audio)
all Audio (including video audio)
+

Drop-down

  • GuiTree.java
@@ -1518,6 +1527,7 @@
all Audio (including video audio)
all Images
+

Drop-down

  • GuiTree.java
@@ -1530,6 +1540,7 @@
all Audio (including video audio)
all Sound clips
+

Drop-down

  • GuiTree.java
@@ -1542,8 +1553,9 @@
all Audio (including video audio)
Details
+

Column name

    -
  • VideoSaverBuilderStrGui.java
  • +
  • SectorBasedVideoSaverBuilderGui.java
  • GuiTree.java
*/ @@ -1555,8 +1567,9 @@
all Audio (including video audio)
Save
+

Column name

    -
  • VideoSaverBuilderStrGui.java
  • +
  • SectorBasedVideoSaverBuilderGui.java
  • GuiTree.java
*/ @@ -1568,8 +1581,9 @@
all Audio (including video audio)
#
+

Column name

    -
  • VideoSaverBuilderStrGui.java
  • +
  • SectorBasedVideoSaverBuilderGui.java
  • GuiTree.java
*/ @@ -1713,6 +1727,7 @@
all Audio (including video audio)
Cancel
+

Button

  • IndexingGui.java
  • SavingGui.java
  • @@ -1726,6 +1741,7 @@
    all Audio (including video audio)
    Close
    +

    Button

    • IndexingGui.java
    • SavingGui.java
    • @@ -1739,6 +1755,7 @@
      all Audio (including video audio)
      Start
      +

      Button

      • IndexingGui.java
      • SavingGui.java
      • @@ -1825,6 +1842,7 @@
        all Audio (including video audio)
        Err
        +

        Column header for the number of errors in the GUI

        • SavingGuiTable.java
        @@ -2002,10 +2020,6 @@
        partial header (2336 bytes/sector) format
        Failed to convert serialized field to int: {0}
        -

        TODO combine with next one

        -
          -
        • SerializedDiscItem.java
        • -
        */ public static @Nonnull ILocalizedMessage SERIALIZATION_FAILED_TO_CONVERT_TO_INT(@Nonnull String badNumber) { return msg("SERIALIZATION_FAILED_TO_CONVERT_TO_INT", "Failed to convert serialized field to int: {0}", badNumber); @@ -2015,12 +2029,21 @@
        partial header (2336 bytes/sector) format
        Failed to convert serialized field to long: {0}
        + */ + public static @Nonnull ILocalizedMessage SERIALIZATION_FAILED_TO_CONVERT_TO_LONG(@Nonnull String badNumber) { + return msg("SERIALIZATION_FAILED_TO_CONVERT_TO_LONG", "Failed to convert serialized field to long: {0}", badNumber); + } + + /** +
        +
        Failed to convert text to number: {0}
        +
        • SerializedDiscItem.java
        */ - public static @Nonnull ILocalizedMessage SERIALIZATION_FAILED_TO_CONVERT_TO_LONG(@Nonnull String badNumber) { - return msg("SERIALIZATION_FAILED_TO_CONVERT_TO_LONG", "Failed to convert serialized field to long: {0}", badNumber); + public static @Nonnull ILocalizedMessage SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER(@Nonnull String badNumber) { + return msg("SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER", "Failed to convert text to number: {0}", badNumber); } /** @@ -2039,6 +2062,7 @@
        partial header (2336 bytes/sector) format
        Failed to convert serialized value to range: {0}
        +

        This is when trying to parse a "range" value, which is supposed to like "number-number" (e.g. "7-14"), but in this case the "range" format isn't right

        • SerializedDiscItem.java
        @@ -2063,6 +2087,7 @@
        partial header (2336 bytes/sector) format
        {0} field not found.
        +

        A .idx line is missing a field, e.g. a video is missing "dimensions"

        • SerializedDiscItem.java
        @@ -2073,14 +2098,14 @@
        partial header (2336 bytes/sector) format
        /**
        -
        Disc format does not match what index says "{0}" != "{1}".
        +
        Disc format "{0}" does not match the format in the index file "{1}"
        • DiscIndex.java
        */ public static @Nonnull ILocalizedMessage CD_FORMAT_MISMATCH(@Nonnull String actualFormatDescription, @Nonnull String expectedFormatDescription) { - return msg("CD_FORMAT_MISMATCH", "Disc format does not match what index says \"{0}\" != \"{1}\".", actualFormatDescription, expectedFormatDescription); + return msg("CD_FORMAT_MISMATCH", "Disc format \"{0}\" does not match the format in the index file \"{1}\"", actualFormatDescription, expectedFormatDescription); } /** @@ -2118,12 +2143,22 @@
        partial header (2336 bytes/sector) format
        /**
        -
        Non-continuous sector header number: {0,number,#} -> {1,number,#}
        +
        Detected corruption at sector {0,number,#}. This may affect identification and conversion.
        • DiscIndex.java
        */ + public static @Nonnull ILocalizedMessage INDEX_SECTOR_CORRUPTED_AT(int sectorNumber) { + return msg("INDEX_SECTOR_CORRUPTED_AT", "Detected corruption at sector {0,number,#}. This may affect identification and conversion.", sectorNumber); + } + + /** +
        +
        Non-continuous sector header number: {0,number,#} -> {1,number,#}
        +
        +

        Sector header numbers should always be sequential. If some number is skipped, this error appears.

        + */ public static @Nonnull ILocalizedMessage INDEX_SECTOR_HEADER_NUM_BREAK(int previousSectorNumber, int currentSectorNumber) { return msg("INDEX_SECTOR_HEADER_NUM_BREAK", "Non-continuous sector header number: {0,number,#} -> {1,number,#}", previousSectorNumber, currentSectorNumber); } @@ -2132,9 +2167,7 @@
        partial header (2336 bytes/sector) format
        Sector {0,number,#} is Mode 1 found among Mode 2 sectors
        -
          -
        • DiscIndex.java
        • -
        +

        This is another case where these is inconsistencies in the sequence of sectors. "Mode 1" and "Mode 2" are sector types, and should never be mixed together.

        */ public static @Nonnull ILocalizedMessage INDEX_MODE1_AMONG_MODE2(int sectorNumber) { return msg("INDEX_MODE1_AMONG_MODE2", "Sector {0,number,#} is Mode 1 found among Mode 2 sectors", sectorNumber); @@ -2144,6 +2177,8 @@
        partial header (2336 bytes/sector) format
        Failed to parse "{0}" because "{1}"
        +

        When trying to open a .idx file, if there is a line that it doesn't recognize or has an error, this is the message that is logged. So 0 is the text of the line, and 1 is the error message. For a silly example:

        +

        Failed to parse "bad line" because "That line makes no sense"

        • DiscIndex.java
        @@ -2168,7 +2203,7 @@
        partial header (2336 bytes/sector) format
        Index contains multiple lines that start with "{0}"
        -

        The lines it's talking about are the lines indicating the source disc file

        +

        The .idx file has a line that described the source disc file format. The line starts with "{0}". There should only be 1 line of this type in a .idx file. In this case, there are more than one, which is weird.

        • DiscIndex.java
        @@ -2206,6 +2241,7 @@
        partial header (2336 bytes/sector) format
        Sector {0,number,#} / {1,number,#} {2,number,#} items found
        +

        Progress bar string

        • DiscIndex.java
        @@ -2365,14 +2401,26 @@
        partial header (2336 bytes/sector) format
        /**
        -
        {0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#} fps = {4,time,m:ss}
        +
        {0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss}
          -
        • DiscItemCrusader.java
        • +
        • DiscItemPacketBasedVideoStream.java
        */ - public static @Nonnull ILocalizedMessage GUI_CRUSADER_VID_DETAILS(int videoWidth, int videoHeight, int frameCount, int framesPerSecond, @Nonnull java.util.Date duration) { - return msg("GUI_CRUSADER_VID_DETAILS", "{0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#} fps = {4,time,m:ss}", videoWidth, videoHeight, frameCount, framesPerSecond, duration); + public static @Nonnull ILocalizedMessage GUI_PACKET_BASED_VID_DETAILS(int videoWidth, int videoHeight, int frameCount, double framesPerSecond, @Nonnull java.util.Date duration) { + return msg("GUI_PACKET_BASED_VID_DETAILS", "{0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss}", videoWidth, videoHeight, frameCount, framesPerSecond, duration); + } + + /** +
        +
        {0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss}, {5,number,#} Hz
        +
        +
          +
        • DiscItemPacketBasedVideoStream.java
        • +
        + */ + public static @Nonnull ILocalizedMessage GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO(int videoWidth, int videoHeight, int frameCount, double framesPerSecond, @Nonnull java.util.Date duration, int audioHz) { + return msg("GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO", "{0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss}, {5,number,#} Hz", videoWidth, videoHeight, frameCount, framesPerSecond, duration, audioHz); } /** @@ -2389,7 +2437,7 @@
        partial header (2336 bytes/sector) format
        {0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss} (or {5,number,#.###} fps = {6,time,m:ss})
    -
  • DiscItemStrVideoStream.java
  • +
  • DiscItemSectorBasedVideoStream.java
*/ public static @Nonnull ILocalizedMessage GUI_STR_VIDEO_DETAILS_UNKNOWN_FPS(int videoWidth, int videoHeight, int frameCount, double doubleSpeedFramesPerSecond, @Nonnull java.util.Date doubleSpeedDuration, double singleSpeedFramesPerSecond, @Nonnull java.util.Date singleSpeedDuration) { @@ -2401,7 +2449,7 @@
partial header (2336 bytes/sector) format
{0,number,#}x{1,number,#}, {2,number,#} frames, {3,number,#.###} fps = {4,time,m:ss}
    -
  • DiscItemStrVideoStream.java
  • +
  • DiscItemSectorBasedVideoStream.java
*/ public static @Nonnull ILocalizedMessage GUI_STR_VIDEO_DETAILS(int videoWidth, int videoHeight, int frameCount, double framesPerSecond, @Nonnull java.util.Date duration) { @@ -2647,7 +2695,6 @@
Adjust volume (default {0,number,#}).
Ignoring invalid format {0}
    -
  • AudioSaverBuilder.java
  • SpuSaverBuilder.java
*/ @@ -2660,7 +2707,6 @@
Adjust volume (default {0,number,#}).
Ignoring invalid volume {0}
    -
  • AudioSaverBuilder.java
  • SpuSaverBuilder.java
*/ @@ -2672,9 +2718,6 @@
Adjust volume (default {0,number,#}).
Ignoring invalid disc speed {0}
-
    -
  • VideoSaverBuilder.java
  • -
*/ public static @Nonnull ILocalizedMessage CMD_IGNORING_INVALID_DISC_SPEED(@Nonnull String badDiscSpeed) { return msg("CMD_IGNORING_INVALID_DISC_SPEED", "Ignoring invalid disc speed {0}", badDiscSpeed); @@ -2728,7 +2771,7 @@
Adjust volume (default {0,number,#}).
Patching sector {0}
-

Combined with next string

+

TODO Combined with next string

  • DiscItemXaAudioStream.java
@@ -2817,6 +2860,7 @@
Adjust volume (default {0,number,#}).
Patching {0}
+

This line comes before the next line

  • DiscItemXaAudioStream.java
@@ -2829,6 +2873,7 @@
Adjust volume (default {0,number,#}).
with {0}
+

This line comes after the previous line

  • DiscItemXaAudioStream.java
@@ -3002,6 +3047,7 @@
Adjust volume (default {0,number,#}).
Found an unexpected number of frames, the frames may be saved in an inconsistent order.
+

When the file is indexed, the number of frames in a video are saved. When saving as a sequence of imgaes, that number is necessary to properly format the file names with extra zeroes. e.g. a video with 99 frames needs all frame numbers to be 2 digits, but a video with 100 frames, needs frame numbers to be 3 digits. If the index says there are 99 frames, but there are actually 100, then the file name "videoframe[100].png" will come before "videoframe[99].png".

  • HeaderFrameNumber.java
  • IndexSectorFrameNumber.java
  • @@ -3092,7 +3138,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Bicubic
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION() { @@ -3105,7 +3151,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BICUBIC_CMDLINE() { @@ -3117,7 +3163,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Bell
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BELL_DESCRIPTION() { @@ -3130,7 +3176,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BELL_CMDLINE() { @@ -3142,7 +3188,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Nearest Neighbor
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION() { @@ -3155,7 +3201,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE() { @@ -3167,7 +3213,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Lanczos3
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION() { @@ -3180,7 +3226,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_LANCZOS3_CMDLINE() { @@ -3192,7 +3238,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Mitchell
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION() { @@ -3205,7 +3251,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_MITCHELL_CMDLINE() { @@ -3217,7 +3263,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Hermite
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_HERMITE_DESCRIPTION() { @@ -3230,7 +3276,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_HERMITE_CMDLINE() { @@ -3242,7 +3288,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    BSpline
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION() { @@ -3255,7 +3301,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BSPLINE_CMDLINE() { @@ -3267,7 +3313,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Bilinear
      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION() { @@ -3280,7 +3326,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    1 word (no spaces) user can type on command-line. Not case sensitive

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_BILINEAR_CMDLINE() { @@ -3293,7 +3339,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    See CHROMA_UPSAMPLE_*_DESCRIPTION and CHROMA_UPSAMPLE_*_CMDLINE

      -
    • MdecDecoder_double_interpolate.java
    • +
    • ChromaUpsample.java
    */ public static @Nonnull ILocalizedMessage CHROMA_UPSAMPLE_CMDLINE_HELP(@Nonnull ILocalizedMessage commandLineId, @Nonnull ILocalizedMessage interplationName) { @@ -3367,7 +3413,6 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Invalid replacement image format {0}
    -

    "mdec" is a file type

    • ReplaceFrameFull.java
    @@ -3400,7 +3445,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    /**
    -
    Unable to compress frame {0} small enough to fit in {1,number,#} bytes!!!
    +
    Unable to compress frame {0} small enough to fit in {1,number,#} bytes
    • ReplaceFrameFull.java
    • @@ -3408,19 +3453,19 @@

      1 word (no spaces) user can type on command-line. Not case sensitive

    */ public static @Nonnull ILocalizedMessage CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH(@Nonnull String frameNumber, int maxSize) { - return msg("CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH", "Unable to compress frame {0} small enough to fit in {1,number,#} bytes!!!", frameNumber, maxSize); + return msg("CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH", "Unable to compress frame {0} small enough to fit in {1,number,#} bytes", frameNumber, maxSize); } /**
    -
    No differences found, skipping.
    +
    No differences found in frame {0}, skipping.
    • ReplaceFramePartial.java
    */ - public static @Nonnull ILocalizedMessage CMD_NO_DIFFERENCE_SKIPPING() { - return msg("CMD_NO_DIFFERENCE_SKIPPING", "No differences found, skipping."); + public static @Nonnull ILocalizedMessage CMD_NO_DIFFERENCE_SKIPPING(@Nonnull String frameNumber) { + return msg("CMD_NO_DIFFERENCE_SKIPPING", "No differences found in frame {0}, skipping.", frameNumber); } /** @@ -3461,14 +3506,14 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    /**
    -
    Replacement frame dimensions smaller than source frame
    +
    Replacement frame dimensions {0,number,#}x{1,number,#} are smaller than source frame {2,number,#}x{3,number,#}
    • ReplaceFramePartial.java
    */ - public static @Nonnull ILocalizedMessage REPLACE_FRAME_DIMENSIONS_TOO_SMALL() { - return msg("REPLACE_FRAME_DIMENSIONS_TOO_SMALL", "Replacement frame dimensions smaller than source frame"); + public static @Nonnull ILocalizedMessage REPLACE_FRAME_DIMENSIONS_TOO_SMALL(int newWidth, int newHeight, int existingWidth, int existingHeight) { + return msg("REPLACE_FRAME_DIMENSIONS_TOO_SMALL", "Replacement frame dimensions {0,number,#}x{1,number,#} are smaller than source frame {2,number,#}x{3,number,#}", newWidth, newHeight, existingWidth, existingHeight); } /** @@ -3500,6 +3545,7 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Invalid root node {0}
    +

    The root xml tag should be "str-replace", but it's "{0}"

    • ReplaceFrames.java
    @@ -3569,9 +3615,6 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Frame is not Lain format
    -
      -
    • BitStreamUncompressor_Lain.java
    • -
    */ public static @Nonnull ILocalizedMessage FRAME_NOT_LAIN() { return msg("FRAME_NOT_LAIN", "Frame is not Lain format"); @@ -3581,9 +3624,6 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Frame is not STRv1 format
    -
      -
    • BitStreamUncompressor_STRv1.java
    • -
    */ public static @Nonnull ILocalizedMessage FRAME_NOT_STRV1() { return msg("FRAME_NOT_STRV1", "Frame is not STRv1 format"); @@ -3593,9 +3633,6 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Frame is not STRv2 format
    -
      -
    • BitStreamUncompressor_STRv2.java
    • -
    */ public static @Nonnull ILocalizedMessage FRAME_NOT_STRV2() { return msg("FRAME_NOT_STRV2", "Frame is not STRv2 format"); @@ -3605,14 +3642,23 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    Frame is not STRv3 format
    -
      -
    • BitStreamUncompressor_STRv3.java
    • -
    */ public static @Nonnull ILocalizedMessage FRAME_NOT_STRV3() { return msg("FRAME_NOT_STRV3", "Frame is not STRv3 format"); } + /** +
    +
    Frame is not {0} format
    +
    +
      +
    • *
    • +
    + */ + public static @Nonnull ILocalizedMessage FRAME_IS_NOT_BITSTREAM_FORMAT(@Nonnull String frameFormatName) { + return msg("FRAME_IS_NOT_BITSTREAM_FORMAT", "Frame is not {0} format", frameFormatName); + } + /**
    Iki frame dimensions do not match sector dimensions: {0,number,#}x{1,number,#} != {2,number,#}x{3,number,#}
    @@ -3648,23 +3694,22 @@

    1 word (no spaces) user can type on command-line. Not case sensitive

    /**
    -
    Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
    +
    Trying to reduce quantization scale of macroblock ({0,number,#},{1,number,#}) to {2,number,#}
    +

    Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic.

    • BitStreamUncompressor_Iki.java
    */ public static @Nonnull ILocalizedMessage IKI_REDUCING_QSCALE_OF_MB_TO_VAL(int macroBlockX, int macroBlockY, int quantizationScale) { - return msg("IKI_REDUCING_QSCALE_OF_MB_TO_VAL", "Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}", macroBlockX, macroBlockY, quantizationScale); + return msg("IKI_REDUCING_QSCALE_OF_MB_TO_VAL", "Trying to reduce quantization scale of macroblock ({0,number,#},{1,number,#}) to {2,number,#}", macroBlockX, macroBlockY, quantizationScale); } /**
    New frame {0} demux size {1,number,#} > max source {2,number,#}, so stopping
    -
      -
    • BitStreamUncompressor_Iki.java
    • -
    +

    Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic.

    */ public static @Nonnull ILocalizedMessage IKI_NEW_FRAME_GT_SRC_STOPPING(@Nonnull String frameNumber, int demuxSize, int sourceSize) { return msg("IKI_NEW_FRAME_GT_SRC_STOPPING", "New frame {0} demux size {1,number,#} > max source {2,number,#}, so stopping", frameNumber, demuxSize, sourceSize); @@ -3672,7 +3717,7 @@
    Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
    -
    New frame {0} demux size {1,number,#} <= max source {2,number,#}
    +
    New frame {0} replacement size {1,number,#} fits within the existing available size {2,number,#}
    • BitStreamUncompressor_Iki.java
    • @@ -3681,7 +3726,7 @@
      Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
      */ public static @Nonnull ILocalizedMessage NEW_FRAME_FITS(@Nonnull String frameNumber, int demuxSize, int sourceSize) { - return msg("NEW_FRAME_FITS", "New frame {0} demux size {1,number,#} <= max source {2,number,#}", frameNumber, demuxSize, sourceSize); + return msg("NEW_FRAME_FITS", "New frame {0} replacement size {1,number,#} fits within the existing available size {2,number,#}", frameNumber, demuxSize, sourceSize); } /** @@ -3708,19 +3753,20 @@
      Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
      -
      Trying luma {0,number,#} chroma {1,number,#}
      +
      Trying to compress with luma quantization scale {0,number,#} and chroma quantization scale {1,number,#}
      +

      Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic.

      • BitStreamUncompressor_Lain.java
      */ public static @Nonnull ILocalizedMessage TRYING_LUMA_CHROMA(int lumaQuantizationScale, int chromaQuantizationScale) { - return msg("TRYING_LUMA_CHROMA", "Trying luma {0,number,#} chroma {1,number,#}", lumaQuantizationScale, chromaQuantizationScale); + return msg("TRYING_LUMA_CHROMA", "Trying to compress with luma quantization scale {0,number,#} and chroma quantization scale {1,number,#}", lumaQuantizationScale, chromaQuantizationScale); } /**
      -
      !!! New frame {0} demux size {1,number,#} > max source {2,number,#} !!!
      +
      New frame {0} replacement size {1,number,#} does not fit in the existing available size {2,number,#}
      • BitStreamUncompressor_Iki.java
      • @@ -3730,19 +3776,19 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        */ public static @Nonnull ILocalizedMessage NEW_FRAME_DOES_NOT_FIT(@Nonnull String frameNumber, int newFrameSize, int sourceFrameSize) { - return msg("NEW_FRAME_DOES_NOT_FIT", "!!! New frame {0} demux size {1,number,#} > max source {2,number,#} !!!", frameNumber, newFrameSize, sourceFrameSize); + return msg("NEW_FRAME_DOES_NOT_FIT", "New frame {0} replacement size {1,number,#} does not fit in the existing available size {2,number,#}", frameNumber, newFrameSize, sourceFrameSize); } /**
        -
        Video format cannot handle the magnitude of the new data for frame {0}
        +
        Replacement image for frame {0} is too detailed to compress
        • BitStreamUncompressor_Lain.java
        */ public static @Nonnull ILocalizedMessage COMPRESS_TOO_MUCH_ENERGY(@Nonnull String frameNumber) { - return msg("COMPRESS_TOO_MUCH_ENERGY", "Video format cannot handle the magnitude of the new data for frame {0}", frameNumber); + return msg("COMPRESS_TOO_MUCH_ENERGY", "Replacement image for frame {0} is too detailed to compress", frameNumber); } /** @@ -3765,14 +3811,14 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        -
        Embedded Crusader audio {0,number,#}Hz
        +
        Embedded audio {0,number,#} Hz
          -
        • VideoSaverBuilderCrusader.java
        • +
        • PacketBasedVideoSaverBuilder.java
        */ - public static @Nonnull ILocalizedMessage EMBEDDED_CRUSADER_AUDIO_HZ(int audioSampleRate) { - return msg("EMBEDDED_CRUSADER_AUDIO_HZ", "Embedded Crusader audio {0,number,#}Hz", audioSampleRate); + public static @Nonnull ILocalizedMessage CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ(int audioSampleRate) { + return msg("CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ", "Embedded audio {0,number,#} Hz", audioSampleRate); } /** @@ -3785,6 +3831,19 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        +
        Policenauts data corruption
        + +
          +
        • DiscIndexerPolicenauts.java
        • +
        • SectorClaimToPolicenauts.java
        • +
        + */ + public static @Nonnull ILocalizedMessage POLICENAUTS_DATA_CORRUPTION() { + return msg("POLICENAUTS_DATA_CORRUPTION", "Policenauts data corruption"); + } + /**
        Crusader: No Remorse audio is corrupted
        @@ -3808,9 +3867,6 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        Sector {0,number,#} chunks in frame changed from {1,number,#} to {2,number,#}
        -
          -
        • SectorFrameBuilder.java
        • -
        */ public static @Nonnull ILocalizedMessage DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO(int sectorNumber, int currentChunkCount, int newChunkCount) { return msg("DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO", "Sector {0,number,#} chunks in frame changed from {1,number,#} to {2,number,#}", sectorNumber, currentChunkCount, newChunkCount); @@ -3820,9 +3876,6 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        Sector {0,number,#} chunk number {1,number,#} >= chunks in frame {2,number,#}
        -
          -
        • SectorFrameBuilder.java
        • -
        */ public static @Nonnull ILocalizedMessage DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME(int sectorNumber, int chunkNumber, int chunksInFrame) { return msg("DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME", "Sector {0,number,#} chunk number {1,number,#} >= chunks in frame {2,number,#}", sectorNumber, chunkNumber, chunksInFrame); @@ -3833,7 +3886,7 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        Frame in sectors {0,number,#}-{1,number,#} is missing chunk {2,number,#}
          -
        • SectorFrameBuilder.java
        • +
        • SectorBasedFrameBuilder.java
        */ public static @Nonnull ILocalizedMessage MISSING_CHUNK_FRAME_IN_SECTORS(int frameStartSector, int frameEndSector, int chunkNumber) { @@ -3842,7 +3895,7 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        -
        Trying to replace a frame with missing chunks??
        +
        Trying to replace an existing corrupted frame
        • DemuxedCrusaderFrame.java
        • @@ -3850,7 +3903,7 @@
          Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
          */ public static @Nonnull ILocalizedMessage CMD_FRAME_TO_REPLACE_MISSING_CHUNKS() { - return msg("CMD_FRAME_TO_REPLACE_MISSING_CHUNKS", "Trying to replace a frame with missing chunks??"); + return msg("CMD_FRAME_TO_REPLACE_MISSING_CHUNKS", "Trying to replace an existing corrupted frame"); } /** @@ -3861,6 +3914,7 @@
          Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
          VDP.java
        • ReplaceFrameFull.java
        • ReplaceFramePartial.java
        • +
        • SectorBasedFrameBuilder.java
        */ public static @Nonnull ILocalizedMessage FRAME_NUM_CORRUPTED(@Nonnull String frameNumber) { @@ -3933,26 +3987,28 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        -
        Frame {0} is ahead of reading by {1,number,#} {1,choice,1#frame|2#frames}.
        +
        Presentation time of frame {0} in video file will be {1,number,#} {1,choice,1#frame|2#frames} ahead of original timing
        +

        Message when saving an .avi and the frames are slightly out of sync with the audio

        • VDP.java
        */ public static @Nonnull ILocalizedMessage FRAME_NUM_AHEAD_OF_READING(@Nonnull String frameNumber, int frameCount) { - return msg("FRAME_NUM_AHEAD_OF_READING", "Frame {0} is ahead of reading by {1,number,#} {1,choice,1#frame|2#frames}.", frameNumber, frameCount); + return msg("FRAME_NUM_AHEAD_OF_READING", "Presentation time of frame {0} in video file will be {1,number,#} {1,choice,1#frame|2#frames} ahead of original timing", frameNumber, frameCount); } /**
        -
        Frame is ahead of reading by {0,number,#} {0,choice,1#frame|2#frames}.
        +
        Presentation time of frame in video file will be {0,number,#} {0,choice,1#frame|2#frames} ahead of original timing
        +

        Message when saving an .avi and the frames are slightly out of sync with the audio

        • VDP.java
        */ public static @Nonnull ILocalizedMessage FRAME_AHEAD_OF_READING(int frameCount) { - return msg("FRAME_AHEAD_OF_READING", "Frame is ahead of reading by {0,number,#} {0,choice,1#frame|2#frames}.", frameCount); + return msg("FRAME_AHEAD_OF_READING", "Presentation time of frame in video file will be {0,number,#} {0,choice,1#frame|2#frames} ahead of original timing", frameCount); } /** @@ -4011,14 +4067,26 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        -
        Writing {0,number,#} blank frame(s) to align audio/video playback.
        +
        Writing {0,number,#} blank {0,choice,1#frame|2#frames} to align audio/video playback.
        • VDP.java
        */ public static @Nonnull ILocalizedMessage WRITING_BLANK_FRAMES_TO_ALIGN_AV(int frameCount) { - return msg("WRITING_BLANK_FRAMES_TO_ALIGN_AV", "Writing {0,number,#} blank frame(s) to align audio/video playback.", frameCount); + return msg("WRITING_BLANK_FRAMES_TO_ALIGN_AV", "Writing {0,number,#} blank {0,choice,1#frame|2#frames} to align audio/video playback.", frameCount); + } + + /** +
        +
        Writing {0,number,#} duplicate {0,choice,1#frame|2#frames} to align audio/video playback.
        +
        +
          +
        • VDP.java
        • +
        + */ + public static @Nonnull ILocalizedMessage WRITING_DUP_FRAMES_TO_ALIGN_AV(int frameCount) { + return msg("WRITING_DUP_FRAMES_TO_ALIGN_AV", "Writing {0,number,#} duplicate {0,choice,1#frame|2#frames} to align audio/video playback.", frameCount); } /** @@ -4037,6 +4105,7 @@
        Trying to reduce Qscale of ({0,number,#},{1,number,#}) to {2,number,#}
        Writing {0,number,#} samples of silence to align audio/video playback.
        +

        TODO combine with next line

        • VDP.java
        @@ -4286,6 +4355,7 @@

        1 word (no spaces) user can type on command-line. Not case sensitive

        {0}-{1}
        +

        Display the range of frame files that will be saved. e.g. "frame[001].png-frame[077].png"

        • VideoSaverBuilder.java
        @@ -4387,6 +4457,7 @@
        With audio item(s):

        See CHROMA_UPSAMPLE_*_DESCRIPTION

        • VideoSaverBuilder.java
        • +
        • Command_Static.java
        */ public static @Nonnull ILocalizedMessage CMD_UPSAMPLE_QUALITY(@Nonnull String upsampleDescription) { @@ -4443,6 +4514,30 @@
        With audio item(s):
        return msg("CMD_CROPPING", "Cropping: {0,choice,0#No|1#Yes}", willCrop); } + /** +
        +
        Video must have even dimensions to save as {0}, increasing size by 1 pixel
        +
        +
          +
        • VideoSaverBuilder.java
        • +
        + */ + public static @Nonnull ILocalizedMessage CMD_VIDEO_MUST_HAVE_EVEN_DIMS(@Nonnull String videoFormatDescription) { + return msg("CMD_VIDEO_MUST_HAVE_EVEN_DIMS", "Video must have even dimensions to save as {0}, increasing size by 1 pixel", videoFormatDescription); + } + + /** +
        +
        Dimensions: {0,number,#}x{1,number,#}
        +
        +
          +
        • VideoSaverBuilder.java
        • +
        + */ + public static @Nonnull ILocalizedMessage CMD_DIMENSIONS(int width, int height) { + return msg("CMD_DIMENSIONS", "Dimensions: {0,number,#}x{1,number,#}", width, height); + } + /**
        Error closing AVI
        @@ -4533,9 +4628,7 @@
        With audio item(s):
        Invalid upsample quality {0}
        -
          -
        • VideoSaverBuilder.java
        • -
        +

        TODO replace this and similar lines with "invalid option/value for {-command}"

        */ public static @Nonnull ILocalizedMessage CMD_UPSAMPLE_QUALITY_INVALID(@Nonnull String badQualityName) { return msg("CMD_UPSAMPLE_QUALITY_INVALID", "Invalid upsample quality {0}", badQualityName); @@ -4545,9 +4638,7 @@
        With audio item(s):
        Invalid decode quality {0}
        -
          -
        • VideoSaverBuilder.java
        • -
        +

        TODO replace this and similar lines with "invalid option/value for {-command}"

        */ public static @Nonnull ILocalizedMessage CMD_DECODE_QUALITY_INVALID(@Nonnull String badQualityName) { return msg("CMD_DECODE_QUALITY_INVALID", "Invalid decode quality {0}", badQualityName); @@ -4608,9 +4699,6 @@
        Decoding quality (default {0}). Options:
        Invalid frame number type {0}
        -
          -
        • VideoSaverBuilder.java
        • -
        */ public static @Nonnull ILocalizedMessage CMD_FRAME_NUMBER_TYPE_INVALID(@Nonnull String badFrameNumberType) { return msg("CMD_FRAME_NUMBER_TYPE_INVALID", "Invalid frame number type {0}", badFrameNumberType); @@ -4618,15 +4706,15 @@
        Decoding quality (default {0}). Options:
        /**
        -
        -frame,-frames # or #-#
        +
        -start #, -end #
        -

        Note that the commands -frame and -frames are hard-coded

        +

        Note that the commands -start and -end are hard-coded

        • VideoSaverBuilder.java
        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_FRAMES() { - return msg("CMD_VIDEO_FRAMES", "-frame,-frames # or #-#"); + return msg("CMD_VIDEO_FRAMES", "-start #, -end #"); } /** @@ -4672,9 +4760,7 @@
        Decoding quality (default {0}). Options:
        Invalid video format {0}
        -
          -
        • VideoSaverBuilder.java
        • -
        +

        TODO replace this and similar lines with "invalid option/value for {-command}"

        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_FORMAT_INVALID(@Nonnull String badFormatString) { return msg("CMD_VIDEO_FORMAT_INVALID", "Invalid video format {0}", badFormatString); @@ -4724,6 +4810,7 @@
        Output video format (default {0}).
             
        Invalid frame(s) {0}
        +

        TODO replace this and similar lines with "invalid option/value for {-command}"

        */ public static @Nonnull ILocalizedMessage CMD_FRAME_RANGE_INVALID(@Nonnull String badFrameNumberString) { return msg("CMD_FRAME_RANGE_INVALID", "Invalid frame(s) {0}", badFrameNumberString); @@ -4735,8 +4822,8 @@
        Invalid frame(s) {0}

        Note that the command -noaud is hard-coded

          -
        • VideoSaverBuilderCrusader.java
        • -
        • VideoSaverBuilderStr.java
        • +
        • PacketBasedVideoSaverBuilder.java
        • +
        • SectorBasedVideoSaverBuilder.java
        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_NOAUD() { @@ -4748,8 +4835,8 @@
        Invalid frame(s) {0}
        Don't save audio.
          -
        • VideoSaverBuilderCrusader.java
        • -
        • VideoSaverBuilderStr.java
        • +
        • PacketBasedVideoSaverBuilder.java
        • +
        • SectorBasedVideoSaverBuilder.java
        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_NOAUD_HELP() { @@ -4761,7 +4848,7 @@
        Invalid frame(s) {0}
        Save audio:
          -
        • VideoSaverBuilderCrusaderGui.java
        • +
        • PacketBasedVideoSaverBuilderGui.java
        */ public static @Nonnull ILocalizedMessage GUI_SAVE_AUDIO_LABEL() { @@ -4927,7 +5014,7 @@
        Invalid frame(s) {0}
        -psxav
          -
        • VideoSaverBuilderStr.java
        • +
        • SectorBasedVideoSaverBuilder.java
        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_PSXAV() { @@ -4939,7 +5026,7 @@
        Invalid frame(s) {0}
        Emulate PSX audio/video timing.
          -
        • VideoSaverBuilderStr.java
        • +
        • SectorBasedVideoSaverBuilder.java
        */ public static @Nonnull ILocalizedMessage CMD_VIDEO_PSXAV_HELP() { @@ -4951,7 +5038,7 @@
        Invalid frame(s) {0}
        Emulate PSX a/v sync:
          -
        • VideoSaverBuilderStrGui.java
        • +
        • SectorBasedVideoSaverBuilderGui.java
        */ public static @Nonnull ILocalizedMessage GUI_EMULATE_PSX_AV_SYNC_LABEL() { @@ -4964,7 +5051,7 @@
        Invalid frame(s) {0}

        Column name is empty in English

          -
        • VideoSaverBuilderStrGui.java
        • +
        • SectorBasedVideoSaverBuilderGui.java
        */ public static @Nonnull ILocalizedMessage GUI_VID_AUDIO_SAVE_ID_COLUMN() { @@ -5011,9 +5098,6 @@
        Invalid frame(s) {0}
        Invalid format {0}
        -
          -
        • TimSaverBuilder.java
        • -
        */ public static @Nonnull ILocalizedMessage CMD_TIM_SAVE_FORMAT_INVALID(@Nonnull String badFileFormat) { return msg("CMD_TIM_SAVE_FORMAT_INVALID", "Invalid format {0}", badFileFormat); @@ -5023,9 +5107,6 @@
        Invalid frame(s) {0}
        Invalid list of palettes {0}
        -
          -
        • TimSaverBuilder.java
        • -
        */ public static @Nonnull ILocalizedMessage CMD_TIM_PALETTE_LIST_INVALID(@Nonnull String badPaletteList) { return msg("CMD_TIM_PALETTE_LIST_INVALID", "Invalid list of palettes {0}", badPaletteList); @@ -5145,7 +5226,7 @@
        Output image format (default {0}). Options:
        Error reading TIM preview
         {0}
        -

        {0} is a multi-line exception stack trace

        +

        {0} is a technical multi-line error (exception stack trace)

        • TimSaverBuilderGui.java
        @@ -5199,6 +5280,32 @@
        Output image format (default {0}). Options:
        return msg("TIM_REPLACE_MULTI_CLUT_UNABLE", "Unable to replace a multi-paletted TIM with a simple image"); } + /** +
        +
        Invalid value "{0}" for {1}
        +
        +

        command' means the command-line flag, for example "-quality"

        +
          +
        • *
        • +
        + */ + public static @Nonnull ILocalizedMessage CMD_INVALID_VALUE_FOR_CMD(@Nonnull String invalidValue, @Nonnull String command) { + return msg("CMD_INVALID_VALUE_FOR_CMD", "Invalid value \"{0}\" for {1}", invalidValue, command); + } + + /** +
        +
        Ignoring invalid value "{0}" for {1}
        +
        +

        command' means the command-line flag, for example "-quality"

        +
          +
        • *
        • +
        + */ + public static @Nonnull ILocalizedMessage CMD_IGNORING_INVALID_VALUE_FOR_CMD(@Nonnull String invalidValue, @Nonnull String command) { + return msg("CMD_IGNORING_INVALID_VALUE_FOR_CMD", "Ignoring invalid value \"{0}\" for {1}", invalidValue, command); + } + /**
        Opening file {0}
        diff --git a/jpsxdec/src/jpsxdec/i18n/MiscResources_it.java b/jpsxdec/src/jpsxdec/i18n/MiscResources_it.java new file mode 100644 index 0000000..312dbb6 --- /dev/null +++ b/jpsxdec/src/jpsxdec/i18n/MiscResources_it.java @@ -0,0 +1,50 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.i18n; + +import java.util.ListResourceBundle; + +/** Italian resources. */ +public class MiscResources_it extends ListResourceBundle { + @Override + protected Object[][] getContents() { + return new Object[][] { + { MiscResources.MAIN_CMDLINE_HELP, "main_cmdline_help_it.dat" } + }; + } +} diff --git a/jpsxdec/src/jpsxdec/i18n/TabularFeedback.java b/jpsxdec/src/jpsxdec/i18n/TabularFeedback.java index 343fe4c..523fe0d 100644 --- a/jpsxdec/src/jpsxdec/i18n/TabularFeedback.java +++ b/jpsxdec/src/jpsxdec/i18n/TabularFeedback.java @@ -121,6 +121,7 @@ public void write(@Nonnull PrintStream ps) { for (ArrayList row : _rows) { iColCount = Math.max(iColCount, row.size()); } + @SuppressWarnings("unchecked") ArrayList[][] aaoCells = new ArrayList[iRowCount][iColCount]; int[] aiRowHeights = new int[iRowCount]; diff --git a/jpsxdec/src/jpsxdec/i18n/Translations.properties b/jpsxdec/src/jpsxdec/i18n/Translations.properties index cd45796..66a8e2f 100644 --- a/jpsxdec/src/jpsxdec/i18n/Translations.properties +++ b/jpsxdec/src/jpsxdec/i18n/Translations.properties @@ -1,5 +1,6 @@ # jPSXdec Translations -# Copyright (c) 2015-2017 Michael Sabin, Víctor González, Sergi Medina +# Copyright (c) 2015-2019 +# Michael Sabin, Víctor González, Sergi Medina, Gianluigi "Infrid" Cusimano # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -55,17 +56,13 @@ CMD_READING_INDEX_FILE=Reading index file {0} #java.io.File fileName CMD_INPUT_FILE_NOT_FOUND=Input file not found {0} -#Trying to look-up and item by its numeric index and the number is invalid (probably negative) -# -#[Command_Items.java] +#Trying to look-up an item by its numeric index and the number is invalid (probably negative) # #String badItemNumber CMD_ITEM_NUMBER_INVALID=Invalid item number\: {0} #Trying to look-up an item by its string index identifier that is invalid (probably contains space) # -#[Command_Items.java] -# #String badItemIdentifier CMD_ITEM_ID_INVALID=Invalid item identifier\: {0} @@ -97,7 +94,7 @@ CMD_DISC_ITEM_NOT_FOUND_NUM=Could not find disc item {0,number,\#} #String discItemId CMD_DISC_ITEM_NOT_FOUND_STR=Could not find disc item {0} -#The next line will display the item info +#The next line printed to the console after this will be the description of the item # #[Command_Items.java] CMD_DETAILED_HELP_FOR=Detailed help for @@ -121,7 +118,7 @@ CMD_PROCESS_COMPLETE=Disc decoding/extracting complete. #double durationInSeconds PROCESS_TIME=Time\: {0,number,\#.\#\#} sec -#[Command_Items.java] +#[Command_Items.java, Command_Static.java] # #int fileCount CMD_NUM_FILES_CREATED={0,choice,0\#No files created|1\#1 file created|2\#'{0,number,\#}' files created} @@ -162,24 +159,34 @@ INDEX_LOG_FILE_BASE_NAME=index #[Command_Items.java] REPLACE_LOG_FILE_BASE_NAME=replace +#e.g. [INFO] Some message +# #[UserFriendlyLogger.java] # #String logLevel,String logMessage USER_LOG_MESSAGE=[{0}] {1} +#e.g. [WARN] Some message BadException +# #String logLevel,String logMessage,String exceptionName USER_LOG_MESSAGE_EXCEPTION=[{0}] {1} {2} +#e.g. [WARN] Some message BadException: Something bad happened +# #String logLevel,String logMessage,String exceptionName,String exceptionMessage USER_LOG_MESSAGE_EXCEPTION_MSG=[{0}] {1} {2} \: {3} +#e.g. [WARN] BadException +# #String logLevel,String exceptionName USER_LOG_EXCEPTION=[{0}] {1} +#e.g. [WARN] BadException: Something bad happened +# #String logLevel,String exceptionName,String exceptionMessage USER_LOG_EXCEPTION_MSG=[{0}] {1} \: {2} -#[Command_CopySect.java] +#Sector range should be in the format "start-end" # #String badSectorRangeString CMD_SECTOR_RANGE_INVALID=Invalid sector range\: {0} @@ -195,8 +202,9 @@ CMD_GENERATING_SECTOR_LIST=Generating sector list #[Command_Static.java] CMD_DIM_OPTION_REQURIED=-dim option required -#[Command_Static.java] -# +#String badDimensionsString +CMD_INVALID_DIMENSIONS=Invalid dimensions\: {0} + #String badQuality CMD_QUALITY_INVALID=Invalid quality {0} @@ -210,16 +218,12 @@ CMD_NOT_TIM=Error\: not a Tim image #See CHROMA_UPSAMPLE_*_DESCRIPTION # -#[Command_Static.java] -# #ILocalizedMessage upsampleDescription CMD_USING_UPSAMPLING=Using upsampling {0} #[Command_Static.java] CMD_TIM_IO_ERR=Error reading or writing TIM file -#[Command_Static.java] -# #String badFormat CMD_FORMAT_INVALID=Invalid format type {0} @@ -236,8 +240,6 @@ CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA=Start java using the -ea option. #java.io.File timFileName CMD_READING_TIM=Reading TIM file {0} -#[Command_Static.java] -# #String badStaticTypeName CMD_STATIC_TYPE_INVALID=Invalid static type\: {0} @@ -252,11 +254,6 @@ CMD_READING_STATIC_FILE=Reading static file {0} #[Command_Static.java] CMD_IMAGE_CONVERT_OK=Image converted successfully -#[Command_Static.java] -# -#String badUpsamplingName -CMD_UPSAMPLING_INVALID=Invalid upsampling {0} - #[Command_Static.java, VideoSaverBuilder.java] # #java.io.File fileName @@ -283,13 +280,9 @@ CMD_BUILDING_INDEX=Building index CMD_DISC_READ_ERROR=Disc read error. -#[CommandLine.java] -# #String badVerbosityLevel CMD_VERBOSE_LVL_INVALID_STR=Invalid verbosity level {0} -#[CommandLine.java] -# #int badVerbosityNumber CMD_VERBOSE_LVL_INVALID_NUM=Invalid verbosity level {0,number,\#} @@ -363,6 +356,11 @@ GUI_OPEN_ANALYZE_DISC_BTN=Open and Analyze File #[Gui.java] GUI_DISC_NO_RAW_HEADERS_WARNING=Disc image does not have raw headers -- audio may not be detected. +#[Gui.java] +# +#String fileName +GUI_DIALOG_COULD_NOT_IDENTIFY_ANYTHING=Could not identify anything in file {0} + #[Gui.java] GUI_OPEN_DISC_DIALOG_TITLE=Select disc image or media file @@ -410,18 +408,28 @@ GUI_SAVE_INDEX_ERR=Error saving index #[Gui.java] GUI_DIRECTORY_LABEL=Directory\: +#Button +# #[Gui.java] GUI_DIR_CHOOSER_BTN=... +#Button +# #[Gui.java] GUI_SELECT_BTN=Select ... +#Button +# #[Gui.java] GUI_COLLAPSE_ALL_BTN=Collapse All +#Button +# #[Gui.java] GUI_EXPAND_ALL_BTN=Expand All +#Button +# #[Gui.java] GUI_SAVE_ALL_SELECTED_BTN=Save All Selected @@ -435,14 +443,20 @@ GUI_NOTHING_IS_MARKED_FOR_SAVING=Nothing is marked for saving. #ILocalizedMessage itemTypeName GUI_APPLY_TO_ALL_BTN=Apply to all {0} +#Dialog box +# #[Gui.java] # #int itemCount GUI_APPLIED_SETTINGS=Applied settings to {0,number,\#} items. +#Dialog box +# #[Gui.java] GUI_SAVE_INDEX_PROMPT=The index has not been saved. Save index? +#Dialog box +# #[Gui.java] GUI_SAVE_INDEX_PROMPT_TITLE=Save index? @@ -456,6 +470,8 @@ GUI_PLAY_TAB=\ Play #[Gui.java] GUI_SAVE_TAB=\ Save +#Button +# #[Gui.java] GUI_PAUSE_BTN=Pause @@ -467,21 +483,33 @@ GUI_BAD_ERROR=Bad error #[Gui.java] ERR_LOADING_INDEX_FILE=Error loading index file +#File dialog format +# #[GuiFileFilters.java] GUI_CD_IMAGE_EXTENSIONS=CD images (*.iso, *.bin, *.img, *.mdf) +#File dialog format +# #[GuiFileFilters.java] GUI_INDEX_EXTENSION=Index files (*.idx) +#File dialog format +# #[GuiFileFilters.java] GUI_PSX_VIDEO_EXTENSIONS=PlayStation video (*.str, *.mov, *.iki, *.ik2) +#File dialog format +# #[GuiFileFilters.java] GUI_ALL_COMPATIBLE_EXTENSIONS=All compatible types +#File dialog format +# #[GuiFileFilters.java] GUI_XA_EXTENSION=PlayStation/CD-i audio (*.xa, *.xai) +#Dialog +# #[BetterFileChooser.java] # #String fileName @@ -501,31 +529,49 @@ GUI_TREE_TYPE_COLUMN=Type #[GuiTree.java] GUI_SELECT_NONE=none +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_VIDEO=all Videos +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_FILES=all Files +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_AUIO_EX_VID=all Audio (excluding video audio) +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_AUDIO_INC_VID=all Audio (including video audio) +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_IMAGES=all Images +#Drop-down +# #[GuiTree.java] GUI_SELECT_ALL_SOUNDS=all Sound clips -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#Column name +# +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_DETAILS_COLUMN=Details -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#Column name +# +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_SAVE_COLUMN=Save -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#Column name +# +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_INDEX_NUMBER_COLUMN=\# #[IndexId.java] @@ -567,12 +613,18 @@ GUI_INDEX_ERRORS_LABEL=Errors\: #[IndexingGui.java] GUI_INDEX_RESULT_SUCCESS=Success\! +#Button +# #[IndexingGui.java, SavingGui.java] GUI_CANCEL_BTN=Cancel +#Button +# #[IndexingGui.java, SavingGui.java] GUI_CLOSE_BTN=Close +#Button +# #[IndexingGui.java, SavingGui.java] GUI_START_BTN=Start @@ -598,6 +650,8 @@ GUI_SAVE_STATUS_CANCELED=Canceled #[SavingGuiTable.java] GUI_SRC_COLUMN=Source +#Column header for the number of errors in the GUI +# #[SavingGuiTable.java] GUI_ERR_COLUMN=Err @@ -646,23 +700,24 @@ FAILED_TO_READ_1_SECTOR=Failed to read at least 1 entire sector. #[SerializedDiscItem.java] EMPTY_SERIALIZED_STRING=Empty serialized string -#TODO combine with next one -# -#[SerializedDiscItem.java] -# #String badNumber SERIALIZATION_FAILED_TO_CONVERT_TO_INT=Failed to convert serialized field to int\: {0} +#String badNumber +SERIALIZATION_FAILED_TO_CONVERT_TO_LONG=Failed to convert serialized field to long\: {0} + #[SerializedDiscItem.java] # #String badNumber -SERIALIZATION_FAILED_TO_CONVERT_TO_LONG=Failed to convert serialized field to long\: {0} +SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER=Failed to convert text to number\: {0} #[SerializedDiscItem.java] # #String lineFromIndexFile SERIALIZATION_FIELD_IMPROPERLY_FORMATTED=Improperly formatted field serialization\: {0} +#This is when trying to parse a "range" value, which is supposed to like "number-number" (e.g. "7-14"), but in this case the "range" format isn't right +# #[SerializedDiscItem.java] # #String badRange @@ -673,6 +728,8 @@ SERIALIZATION_FAILED_TO_CONVERT_TO_RANGE=Failed to convert serialized value to r #String lineFromIndexFile SERIALIZATION_MISSING_REQUIRED_FIELDS=Line missing vital fields {0} +#A .idx line is missing a field, e.g. a video is missing "dimensions" +# #[SerializedDiscItem.java] # #String missingField @@ -681,7 +738,7 @@ SERIALIZATION_FIELD_NOT_FOUND={0} field not found. #[DiscIndex.java] # #String actualFormatDescription,String expectedFormatDescription -CD_FORMAT_MISMATCH=Disc format does not match what index says "{0}" \!\= "{1}". +CD_FORMAT_MISMATCH=Disc format "{0}" does not match the format in the index file "{1}" #[DiscIndex.java] INDEX_HEADER_MISSING=Missing proper index header. @@ -695,14 +752,22 @@ INDEX_SECTOR_CORRUPTED=Detected corruption in sector {0,number,\#}. This may aff #[DiscIndex.java] # +#int sectorNumber +INDEX_SECTOR_CORRUPTED_AT=Detected corruption at sector {0,number,\#}. This may affect identification and conversion. + +#Sector header numbers should always be sequential. If some number is skipped, this error appears. +# #int previousSectorNumber,int currentSectorNumber INDEX_SECTOR_HEADER_NUM_BREAK=Non-continuous sector header number\: {0,number,\#} -> {1,number,\#} -#[DiscIndex.java] +#This is another case where these is inconsistencies in the sequence of sectors. "Mode 1" and "Mode 2" are sector types, and should never be mixed together. # #int sectorNumber INDEX_MODE1_AMONG_MODE2=Sector {0,number,\#} is Mode 1 found among Mode 2 sectors +#When trying to open a .idx file, if there is a line that it doesn't recognize or has an error, this is the message that is logged. So 0 is the text of the line, and 1 is the error message. For a silly example: +#Failed to parse "bad line" because "That line makes no sense" +# #[DiscIndex.java] # #String lineFromIndexFile,ILocalizedMessage localizedErrorMessage @@ -713,7 +778,7 @@ INDEX_PARSE_LINE_FAIL=Failed to parse "{0}" because "{1}" #String lineFromIndexFile INDEX_UNHANDLED_LINE=Line not recognized {0} -#The lines it's talking about are the lines indicating the source disc file +#The .idx file has a line that described the source disc file format. The line starts with "{0}". There should only be 1 line of this type in a .idx file. In this case, there are more than one, which is weird. # #[DiscIndex.java] # @@ -730,6 +795,8 @@ INDEX_NO_CD=Index is missing a line that starts with "{0}", and no source file w #[DiscIndex.java] INDEX_INCONSTSTENCIES=Found inconsistencies in the index, has it been modified? +#Progress bar string +# #[DiscIndex.java] # #int currentSectorNumber,int totalSectorCount,int itemsFound @@ -785,20 +852,25 @@ ITEM_TYPE_SOUND=Sound clip #[DiscItem.java] ITEM_TYPE_SOUND_APPLY=Sound clips -#[DiscItemCrusader.java] +#[DiscItemPacketBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration +GUI_PACKET_BASED_VID_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} frames, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} + +#[DiscItemPacketBasedVideoStream.java] # -#int videoWidth,int videoHeight,int frameCount,int framesPerSecond,java.util.Date duration -GUI_CRUSADER_VID_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} frames, {3,number,\#} fps \= {4,time,m\:ss} +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration,int audioHz +GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO={0,number,\#}x{1,number,\#}, {2,number,\#} frames, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss}, {5,number,\#} Hz #java.util.Date duration,int sampleRate GUI_SQUARE_AUDIO_DETAILS={0,time,m\:ss}, {1,number,\#} Hz Stereo -#[DiscItemStrVideoStream.java] +#[DiscItemSectorBasedVideoStream.java] # #int videoWidth,int videoHeight,int frameCount,double doubleSpeedFramesPerSecond,java.util.Date doubleSpeedDuration,double singleSpeedFramesPerSecond,java.util.Date singleSpeedDuration GUI_STR_VIDEO_DETAILS_UNKNOWN_FPS={0,number,\#}x{1,number,\#}, {2,number,\#} frames, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} (or {5,number,\#.\#\#\#} fps \= {6,time,m\:ss}) -#[DiscItemStrVideoStream.java] +#[DiscItemSectorBasedVideoStream.java] # #int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration GUI_STR_VIDEO_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} frames, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} @@ -889,18 +961,16 @@ CMD_AUDIO_VOL=-vol <0-100> #int defaultVolumeLevel CMD_AUDIO_VOL_HELP=Adjust volume (default {0,number,\#}). -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidFormatName CMD_IGNORING_INVALID_FORMAT=Ignoring invalid format {0} -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidVolume CMD_IGNORING_INVALID_VOLUME=Ignoring invalid volume {0} -#[VideoSaverBuilder.java] -# #String badDiscSpeed CMD_IGNORING_INVALID_DISC_SPEED=Ignoring invalid disc speed {0} @@ -918,7 +988,7 @@ WRITING_SAMPLES_TO_SECTOR=Writing samples starting at {0,number,\#} to sector se #int sectorNumber CMD_PATCHING_SECTOR_NUMBER=Patching sector {0,number,\#} -#Combined with next string +#TODO Combined with next string # #[DiscItemXaAudioStream.java] # @@ -957,11 +1027,15 @@ SPU_ADPCM_CORRUPTED=Audio corrupted near sector {0,number,\#} affecting samples #java.util.Date duration,int audioSampleRate,int audioChannelCount GUI_AUDIO_DESCRIPTION={0,time,m\:ss}, {1,number,\#} Hz {2,choice,1\#Mono|2\#Stereo} +#This line comes before the next line +# #[DiscItemXaAudioStream.java] # #String discItemDescription CMD_PATCHING_DISC_ITEM=Patching {0} +#This line comes after the previous line +# #[DiscItemXaAudioStream.java] # #String otherDiscItemDescription @@ -1026,6 +1100,8 @@ FRAME_NUM_FORMAT_INVALID=Invalid frame number format {0} #int frameNumber FRAME_MISSING_FRAME_NUMBER_HEADER=Frame {0,number,\#} missing frame number information +#When the file is indexed, the number of frames in a video are saved. When saving as a sequence of imgaes, that number is necessary to properly format the file names with extra zeroes. e.g. a video with 99 frames needs all frame numbers to be 2 digits, but a video with 100 frames, needs frame numbers to be 3 digits. If the index says there are 99 frames, but there are actually 100, then the file name "videoframe[100].png" will come before "videoframe[99].png". +# #[HeaderFrameNumber.java, IndexSectorFrameNumber.java, VideoFileNameFormatter.java] FRAMES_UNEXPECTED_NUMBER=Found an unexpected number of frames, the frames may be saved in an inconsistent order. @@ -1053,73 +1129,73 @@ QUALITY_PSX_DESCRIPTION=Emulate PSX (low) quality #[MdecDecodeQuality.java] QUALITY_PSX_COMMAND=psx -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION=Bicubic #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_CMDLINE=Bicubic -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_DESCRIPTION=Bell #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_CMDLINE=Bell -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION=Nearest Neighbor #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE=NearestNeighbor -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION=Lanczos3 #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_CMDLINE=Lanczos3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION=Mitchell #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_CMDLINE=Mitchell -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_DESCRIPTION=Hermite #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_CMDLINE=Hermite -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION=BSpline #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_CMDLINE=BSpline -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION=Bilinear #1 word (no spaces) user can type on command-line. Not case sensitive # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_CMDLINE=Bilinear #See CHROMA_UPSAMPLE_*_DESCRIPTION and CHROMA_UPSAMPLE_*_CMDLINE # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] # #ILocalizedMessage commandLineId,ILocalizedMessage interplationName CHROMA_UPSAMPLE_CMDLINE_HELP={0} ({1}) @@ -1155,8 +1231,6 @@ REPLACE_INCOMPLETE_MDEC=Incomplete mdec file {0} for frame {1} #String mdecFileName,String frameNumber REPLACE_CORRUPTED_MDEC=Corrupted mdec file {0} for frame {1} -#"mdec" is a file type -# #[ReplaceFrameFull.java] # #String badFormatName @@ -1174,10 +1248,12 @@ CMD_UNABLE_TO_IDENTIFY_FRAME_TYPE=Unable to identify frame type #[ReplaceFrameFull.java, ReplaceFramePartial.java] # #String frameNumber,int maxSize -CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=Unable to compress frame {0} small enough to fit in {1,number,\#} bytes\!\!\! +CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=Unable to compress frame {0} small enough to fit in {1,number,\#} bytes #[ReplaceFramePartial.java] -CMD_NO_DIFFERENCE_SKIPPING=No differences found, skipping. +# +#String frameNumber +CMD_NO_DIFFERENCE_SKIPPING=No differences found in frame {0}, skipping. #[ReplaceFramePartial.java] # @@ -1193,7 +1269,9 @@ CMD_ENTIRE_FRAME_DIFFERENT=Warning\: Entire frame is different. REPLACE_UNABLE_READ_IMAGE=Unable to load {0} as image #[ReplaceFramePartial.java] -REPLACE_FRAME_DIMENSIONS_TOO_SMALL=Replacement frame dimensions smaller than source frame +# +#int newWidth,int newHeight,int existingWidth,int existingHeight +REPLACE_FRAME_DIMENSIONS_TOO_SMALL=Replacement frame dimensions {0,number,\#}x{1,number,\#} are smaller than source frame {2,number,\#}x{3,number,\#} #[ReplaceFrames.java] # @@ -1207,6 +1285,8 @@ CMD_REPLACING_FRAME_WITH_FILE=Replacing frame {0} with {1} #String xmlErrorInEnglish REPLACE_FRAME_XML_ERROR=Error with frame replacement xml\: {0} +#The root xml tag should be "str-replace", but it's "{0}" +# #[ReplaceFrames.java] # #String xmlRootNodeName @@ -1228,18 +1308,19 @@ REPLACE_FRAME_TYPE_NOT_IKI=Frame type is not iki FRAME_NOT_IKI=Frame is not Iki format -#[BitStreamUncompressor_Lain.java] FRAME_NOT_LAIN=Frame is not Lain format -#[BitStreamUncompressor_STRv1.java] FRAME_NOT_STRV1=Frame is not STRv1 format -#[BitStreamUncompressor_STRv2.java] FRAME_NOT_STRV2=Frame is not STRv2 format -#[BitStreamUncompressor_STRv3.java] FRAME_NOT_STRV3=Frame is not STRv3 format +#[*] +# +#String frameFormatName +FRAME_IS_NOT_BITSTREAM_FORMAT=Frame is not {0} format + #[SectorIkiVideo.java] # #int sourceWidth,int sourceHeight,int replaceWidth,int replaceHeight @@ -1250,12 +1331,14 @@ REPLACE_FRAME_TYPE_NOT_LAIN=Incompatible frame data for Lain UNEXPECTED_END_OF_AUDIO=Unexpected end of audio data +#Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic. +# #[BitStreamUncompressor_Iki.java] # #int macroBlockX,int macroBlockY,int quantizationScale -IKI_REDUCING_QSCALE_OF_MB_TO_VAL=Trying to reduce Qscale of ({0,number,\#},{1,number,\#}) to {2,number,\#} +IKI_REDUCING_QSCALE_OF_MB_TO_VAL=Trying to reduce quantization scale of macroblock ({0,number,\#},{1,number,\#}) to {2,number,\#} -#[BitStreamUncompressor_Iki.java] +#Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic. # #String frameNumber,int demuxSize,int sourceSize IKI_NEW_FRAME_GT_SRC_STOPPING=New frame {0} demux size {1,number,\#} > max source {2,number,\#}, so stopping @@ -1263,7 +1346,7 @@ IKI_NEW_FRAME_GT_SRC_STOPPING=New frame {0} demux size {1,number,\#} > max sourc #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java] # #String frameNumber,int demuxSize,int sourceSize -NEW_FRAME_FITS=New frame {0} demux size {1,number,\#} <\= max source {2,number,\#} +NEW_FRAME_FITS=New frame {0} replacement size {1,number,\#} fits within the existing available size {2,number,\#} #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_STRv2.java] # @@ -1272,20 +1355,22 @@ TRYING_QSCALE=Trying {0,number,\#} END_OF_STREAM=End of stream +#Overly technical message when trying to replace a video frame. I don't really expect anyone to understand what it means. Probably should repalce it with something more generic. +# #[BitStreamUncompressor_Lain.java] # #int lumaQuantizationScale,int chromaQuantizationScale -TRYING_LUMA_CHROMA=Trying luma {0,number,\#} chroma {1,number,\#} +TRYING_LUMA_CHROMA=Trying to compress with luma quantization scale {0,number,\#} and chroma quantization scale {1,number,\#} #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java, ReplaceFrameFull.java] # #String frameNumber,int newFrameSize,int sourceFrameSize -NEW_FRAME_DOES_NOT_FIT=\!\!\! New frame {0} demux size {1,number,\#} > max source {2,number,\#} \!\!\! +NEW_FRAME_DOES_NOT_FIT=New frame {0} replacement size {1,number,\#} does not fit in the existing available size {2,number,\#} #[BitStreamUncompressor_Lain.java] # #String frameNumber -COMPRESS_TOO_MUCH_ENERGY=Video format cannot handle the magnitude of the new data for frame {0} +COMPRESS_TOO_MUCH_ENERGY=Replacement image for frame {0} is too detailed to compress #int currentWidth,int newWidth INCONSISTENT_WIDTH=Inconsistent width {0,number,\#} \!\= {1,number,\#} @@ -1293,39 +1378,38 @@ INCONSISTENT_WIDTH=Inconsistent width {0,number,\#} \!\= {1,number,\#} #int currentHeight,int newHeight INCONSISTENT_HEIGHT=Inconsistent height {0,number,\#} \!\= {1,number,\#} -#[VideoSaverBuilderCrusader.java] +#[PacketBasedVideoSaverBuilder.java] # #int audioSampleRate -EMBEDDED_CRUSADER_AUDIO_HZ=Embedded Crusader audio {0,number,\#}Hz +CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ=Embedded audio {0,number,\#} Hz #"Crusader: No Remorse" is the name of a game CRUSADER_VIDEO_CORRUPTED=Crusader\: No Remorse video is corrupted +#[DiscIndexerPolicenauts.java, SectorClaimToPolicenauts.java] +POLICENAUTS_DATA_CORRUPTION=Policenauts data corruption + #"Crusader: No Remorse" is the name of a game CRUSADER_AUDIO_CORRUPTED=Crusader\: No Remorse audio is corrupted #String frameNumber,int chunkNumber MISSING_CHUNK=Frame {0} chunk {1,number,\#} missing. -#[SectorFrameBuilder.java] -# #int sectorNumber,int currentChunkCount,int newChunkCount DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO=Sector {0,number,\#} chunks in frame changed from {1,number,\#} to {2,number,\#} -#[SectorFrameBuilder.java] -# #int sectorNumber,int chunkNumber,int chunksInFrame DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME=Sector {0,number,\#} chunk number {1,number,\#} >\= chunks in frame {2,number,\#} -#[SectorFrameBuilder.java] +#[SectorBasedFrameBuilder.java] # #int frameStartSector,int frameEndSector,int chunkNumber MISSING_CHUNK_FRAME_IN_SECTORS=Frame in sectors {0,number,\#}-{1,number,\#} is missing chunk {2,number,\#} #[DemuxedCrusaderFrame.java, SectorBasedFrameReplace.java] -CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=Trying to replace a frame with missing chunks?? +CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=Trying to replace an existing corrupted frame -#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java] +#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java, SectorBasedFrameBuilder.java] # #String frameNumber FRAME_NUM_CORRUPTED=Error with frame {0}\: Frame is corrupted @@ -1349,15 +1433,19 @@ UNABLE_TO_DETERMINE_FRAME_TYPE_FRM=Error with frame {0}\: Unable to determine fr #[VDP.java] UNABLE_TO_DETERMINE_FRAME_TYPE=Error\: Unable to determine frame type. +#Message when saving an .avi and the frames are slightly out of sync with the audio +# #[VDP.java] # #String frameNumber,int frameCount -FRAME_NUM_AHEAD_OF_READING=Frame {0} is ahead of reading by {1,number,\#} {1,choice,1\#frame|2\#frames}. +FRAME_NUM_AHEAD_OF_READING=Presentation time of frame {0} in video file will be {1,number,\#} {1,choice,1\#frame|2\#frames} ahead of original timing +#Message when saving an .avi and the frames are slightly out of sync with the audio +# #[VDP.java] # #int frameCount -FRAME_AHEAD_OF_READING=Frame is ahead of reading by {0,number,\#} {0,choice,1\#frame|2\#frames}. +FRAME_AHEAD_OF_READING=Presentation time of frame in video file will be {0,number,\#} {0,choice,1\#frame|2\#frames} ahead of original timing #[VDP.java] # @@ -1381,13 +1469,20 @@ JPEG_ENCODER_FRAME_FAIL_NO_FRAME=The simple jPSXdec JPEG encoder can't handle fr #[VDP.java] # #int frameCount -WRITING_BLANK_FRAMES_TO_ALIGN_AV=Writing {0,number,\#} blank frame(s) to align audio/video playback. +WRITING_BLANK_FRAMES_TO_ALIGN_AV=Writing {0,number,\#} blank {0,choice,1\#frame|2\#frames} to align audio/video playback. + +#[VDP.java] +# +#int frameCount +WRITING_DUP_FRAMES_TO_ALIGN_AV=Writing {0,number,\#} duplicate {0,choice,1\#frame|2\#frames} to align audio/video playback. #[VDP.java] # #java.io.File fileName,String frameNumber FRAME_WRITE_ERR=Error writing file {0} for frame {1} +#TODO combine with next line +# #[VDP.java] # #long sampleCount @@ -1470,6 +1565,8 @@ VID_AVI_YUV_DESCRIPTION=AVI\: YUV #[VideoFormat.java] VID_AVI_YUV_COMMAND=avi\:yuv +#Display the range of frame files that will be saved. e.g. "frame[001].png-frame[077].png" +# #[VideoSaverBuilder.java] # #java.io.File startFileName,java.io.File endFileName @@ -1508,7 +1605,7 @@ CMD_NO_AUDIO=No audio #See CHROMA_UPSAMPLE_*_DESCRIPTION # -#[VideoSaverBuilder.java] +#[VideoSaverBuilder.java, Command_Static.java] # #String upsampleDescription CMD_UPSAMPLE_QUALITY=Chroma upsampling\: {0} @@ -1537,6 +1634,16 @@ CMD_FRAME_RANGE_AFTER=Skip frames after {0} #int willCrop CMD_CROPPING=Cropping\: {0,choice,0\#No|1\#Yes} +#[VideoSaverBuilder.java] +# +#String videoFormatDescription +CMD_VIDEO_MUST_HAVE_EVEN_DIMS=Video must have even dimensions to save as {0}, increasing size by 1 pixel + +#[VideoSaverBuilder.java] +# +#int width,int height +CMD_DIMENSIONS=Dimensions\: {0,number,\#}x{1,number,\#} + AVI_CLOSE_ERR=Error closing AVI #[VideoSaverBuilder.java] @@ -1571,12 +1678,12 @@ CMD_VIDEO_UP=-up #ILocalizedMessage defaultUpsamplingMethod CMD_VIDEO_UP_HELP=Chroma upsampling method\n(default {0}). Options\: -#[VideoSaverBuilder.java] +#TODO replace this and similar lines with "invalid option/value for {-command}" # #String badQualityName CMD_UPSAMPLE_QUALITY_INVALID=Invalid upsample quality {0} -#[VideoSaverBuilder.java] +#TODO replace this and similar lines with "invalid option/value for {-command}" # #String badQualityName CMD_DECODE_QUALITY_INVALID=Invalid decode quality {0} @@ -1601,15 +1708,13 @@ CMD_VIDEO_NOCROP=-nocrop #[VideoSaverBuilder.java] CMD_VIDEO_NOCROP_HELP=Don't crop data around unused frame edges. -#[VideoSaverBuilder.java] -# #String badFrameNumberType CMD_FRAME_NUMBER_TYPE_INVALID=Invalid frame number type {0} -#Note that the commands -frame and -frames are hard-coded +#Note that the commands -start and -end are hard-coded # #[VideoSaverBuilder.java] -CMD_VIDEO_FRAMES=-frame,-frames \# or \#-\# +CMD_VIDEO_FRAMES=-start \#, -end \# #[VideoSaverBuilder.java] CMD_VIDEO_FRAMES_HELP=Process only frames in range. @@ -1626,7 +1731,7 @@ CMD_VIDEO_NUM=-num #ILocalizedMessage frameNumberType CMD_VIDEO_NUM_HELP=Frame number to use when saving image sequence\n(default {0}). Options\: -#[VideoSaverBuilder.java] +#TODO replace this and similar lines with "invalid option/value for {-command}" # #String badFormatString CMD_VIDEO_FORMAT_INVALID=Invalid video format {0} @@ -1648,18 +1753,20 @@ CMD_VIDEO_VF_HELP=Output video format (default {0}).\nOptions\: #[VideoSaverBuilder.java] CMD_VIDEO_HEADER_FRAME_NUMBER_UNSUPPORTED=Video does not support indexing frames by header frame number, -start -end -num ignored +#TODO replace this and similar lines with "invalid option/value for {-command}" +# #String badFrameNumberString CMD_FRAME_RANGE_INVALID=Invalid frame(s) {0} #Note that the command -noaud is hard-coded # -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD=-noaud -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD_HELP=Don't save audio. -#[VideoSaverBuilderCrusaderGui.java] +#[PacketBasedVideoSaverBuilderGui.java] GUI_SAVE_AUDIO_LABEL=Save audio\: #[VideoSaverPanel.java] @@ -1708,18 +1815,18 @@ GUI_CROP_CHECKBOX=Crop #[VideoSaverPanel.java] GUI_CHROMA_UPSAMPLING_LABEL=Chroma upsampling\: -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV=-psxav -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV_HELP=Emulate PSX audio/video timing. -#[VideoSaverBuilderStrGui.java] +#[SectorBasedVideoSaverBuilderGui.java] GUI_EMULATE_PSX_AV_SYNC_LABEL=Emulate PSX a/v sync\: #Column name is empty in English # -#[VideoSaverBuilderStrGui.java] +#[SectorBasedVideoSaverBuilderGui.java] GUI_VID_AUDIO_SAVE_ID_COLUMN= #[TimPaletteSelector.java] @@ -1735,13 +1842,9 @@ CMD_PALETTE_IMAGE_SAVE_FAIL=Unable to write image file {0} for palette {1,number #String fileFormat CMD_TIM_SAVE_FORMAT=Format\: {0} -#[TimSaverBuilder.java] -# #String badFileFormat CMD_TIM_SAVE_FORMAT_INVALID=Invalid format {0} -#[TimSaverBuilder.java] -# #String badPaletteList CMD_TIM_PALETTE_LIST_INVALID=Invalid list of palettes {0} @@ -1784,7 +1887,7 @@ TIM_DATA_NOT_FOUND=TIM image data not found #[TimSaverBuilderGui.java] GUI_TIM_SAVE_FORMAT_LABEL=Format\: -#{0} is a multi-line exception stack trace +#{0} is a technical multi-line error (exception stack trace) # #[TimSaverBuilderGui.java] # @@ -1808,6 +1911,20 @@ TIM_INCOMPATIBLE=New TIM format "{0}" does not match existing TIM format "{1}" TIM_REPLACE_MULTI_CLUT_UNABLE=Unable to replace a multi-paletted TIM with a simple image +#command' means the command-line flag, for example "-quality" +# +#[*] +# +#String invalidValue,String command +CMD_INVALID_VALUE_FOR_CMD=Invalid value "{0}" for {1} + +#command' means the command-line flag, for example "-quality" +# +#[*] +# +#String invalidValue,String command +CMD_IGNORING_INVALID_VALUE_FOR_CMD=Ignoring invalid value "{0}" for {1} + #[*] # #String fileName diff --git a/jpsxdec/src/jpsxdec/i18n/Translations_es.properties b/jpsxdec/src/jpsxdec/i18n/Translations_es.properties index e279942..163ea23 100644 --- a/jpsxdec/src/jpsxdec/i18n/Translations_es.properties +++ b/jpsxdec/src/jpsxdec/i18n/Translations_es.properties @@ -1,5 +1,6 @@ # jPSXdec Translations -# Copyright (c) 2015-2017 Michael Sabin, Víctor González, Sergi Medina +# Copyright (c) 2015-2019 +# Michael Sabin, Víctor González, Sergi Medina, Gianluigi "Infrid" Cusimano # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,13 +48,9 @@ CMD_READING_INDEX_FILE=Leyendo archivo de \u00EDndice {0} #java.io.File fileName CMD_INPUT_FILE_NOT_FOUND=No se ha encontrado el archivo de entrada {0} -#[Command_Items.java] -# #String badItemNumber CMD_ITEM_NUMBER_INVALID=N\u00FAmero de elemento no v\u00E1lido\: {0} -#[Command_Items.java] -# #String badItemIdentifier CMD_ITEM_ID_INVALID=Identificador de elemento no v\u00E1lido\: {0} @@ -105,7 +102,7 @@ CMD_PROCESS_COMPLETE=Decodificaci\u00F3n/extracci\u00F3n del disco terminada. #double durationInSeconds PROCESS_TIME=Tiempo\: {0,number,\#.\#\#} s -#[Command_Items.java] +#[Command_Items.java, Command_Static.java] # #int fileCount CMD_NUM_FILES_CREATED={0,choice,0\#No se han creado archivos|1\#Se ha creado un archivo|2\#Se han creado '{0,number,\#}' archivos} @@ -156,8 +153,6 @@ USER_LOG_EXCEPTION=[{0}] {1} #String logLevel,String exceptionName,String exceptionMessage USER_LOG_EXCEPTION_MSG=[{0}] {1} \: {2} -#[Command_CopySect.java] -# #String badSectorRangeString CMD_SECTOR_RANGE_INVALID=Rango del sector no v\u00E1lido\: {0} @@ -172,8 +167,9 @@ CMD_GENERATING_SECTOR_LIST=Generando lista de sectores #[Command_Static.java] CMD_DIM_OPTION_REQURIED=se requiere la opci\u00F3n -dim -#[Command_Static.java] -# +#String badDimensionsString +CMD_INVALID_DIMENSIONS=Dimensiones no v\u00E1lidas\: {0} + #String badQuality CMD_QUALITY_INVALID=Calidad no v\u00E1lida {0} @@ -188,16 +184,12 @@ CMD_NOT_TIM=Error\: No es una imagen TIM #It's impossible to translate upsampling to Spanish in one word, but aumento de muestreo would be the most approximate translation, I think. #- VICTOR: Interpolación would be a better approach -> http://www.proz.com/kudoz/english_to_spanish/computers_software/5731822-upsampling.html # -#[Command_Static.java] -# #ILocalizedMessage upsampleDescription CMD_USING_UPSAMPLING=Utilizando la interpolaci\u00F3n {0} #[Command_Static.java] CMD_TIM_IO_ERR=Error al leer o escribir el archivo TIM -#[Command_Static.java] -# #String badFormat CMD_FORMAT_INVALID=Tipo de formato no v\u00E1lido {0} @@ -214,8 +206,6 @@ CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA=Inicia java con la opci\u00F3n -ea. #java.io.File timFileName CMD_READING_TIM=Leyendo el archivo TIM {0} -#[Command_Static.java] -# #String badStaticTypeName CMD_STATIC_TYPE_INVALID=Tipo est\u00E1tico no v\u00E1lido\: {0} @@ -230,11 +220,6 @@ CMD_READING_STATIC_FILE=Leyendo archivo est\u00E1tico {0} #[Command_Static.java] CMD_IMAGE_CONVERT_OK=Imagen convertida con \u00E9xito -#[Command_Static.java] -# -#String badUpsamplingName -CMD_UPSAMPLING_INVALID=Interpolaci\u00F3n no v\u00E1lida {0} - #[Command_Static.java, VideoSaverBuilder.java] # #java.io.File fileName @@ -261,13 +246,9 @@ CMD_BUILDING_INDEX=Creando \u00EDndice CMD_DISC_READ_ERROR=Error al leer el disco. -#[CommandLine.java] -# #String badVerbosityLevel CMD_VERBOSE_LVL_INVALID_STR=Nivel de verbosidad no v\u00E1lido {0} -#[CommandLine.java] -# #int badVerbosityNumber CMD_VERBOSE_LVL_INVALID_NUM=Nivel de verbosidad no v\u00E1lido {0,number,\#} @@ -333,6 +314,11 @@ GUI_OPEN_ANALYZE_DISC_BTN=Abrir y analizar archivo #[Gui.java] GUI_DISC_NO_RAW_HEADERS_WARNING=La imagen del disco no tiene encabezados en bruto\: puede que no se detecten sus audios. +#[Gui.java] +# +#String fileName +GUI_DIALOG_COULD_NOT_IDENTIFY_ANYTHING=El archivo {0} no contiene elementos identificables. + #[Gui.java] GUI_OPEN_DISC_DIALOG_TITLE=Selecciona una imagen de disco o un archivo multimedia @@ -476,13 +462,13 @@ GUI_SELECT_ALL_IMAGES=todas las im\u00E1genes #[GuiTree.java] GUI_SELECT_ALL_SOUNDS=todos los clips de sonido -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_DETAILS_COLUMN=Detalles -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_SAVE_COLUMN=Guardar -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_INDEX_NUMBER_COLUMN=N.\u00BA #[IndexId.java] @@ -603,15 +589,16 @@ FAILED_TO_READ_1_SECTOR=Error al leer al menos un sector entero. #[SerializedDiscItem.java] EMPTY_SERIALIZED_STRING=Cadena serializada vac\u00EDa -#[SerializedDiscItem.java] -# #String badNumber SERIALIZATION_FAILED_TO_CONVERT_TO_INT=Error al convertir el campo serializado a integral\: {0} +#String badNumber +SERIALIZATION_FAILED_TO_CONVERT_TO_LONG=Error al convertir el campo serializado a longitud\: {0} + #[SerializedDiscItem.java] # #String badNumber -SERIALIZATION_FAILED_TO_CONVERT_TO_LONG=Error al convertir el campo serializado a longitud\: {0} +SERIALIZATION_FAILED_TO_CONVERT_TO_NUMBER=Error al convertir el texto a un valor num\u00E9rico\: {0} #[SerializedDiscItem.java] # @@ -636,7 +623,7 @@ SERIALIZATION_FIELD_NOT_FOUND=Campo {0} no encontrado. #[DiscIndex.java] # #String actualFormatDescription,String expectedFormatDescription -CD_FORMAT_MISMATCH=El formato de disco no coincide con lo que dice el \u00EDndice \u00AB{0}\u00BB \!\= \u00AB{1}\u00BB. +CD_FORMAT_MISMATCH=El formato de disco \u00AB{0}\u00BB no coincide el formato indicado en el \u00EDndice \u00AB{1}\u00BB. #[DiscIndex.java] INDEX_HEADER_MISSING=El encabezado del \u00EDndice no es correcto. @@ -650,11 +637,12 @@ INDEX_SECTOR_CORRUPTED=Corrupci\u00F3n detectada en el sector {0,number,\#}. Pod #[DiscIndex.java] # +#int sectorNumber +INDEX_SECTOR_CORRUPTED_AT=Corrupci\u00F3n detectada en el sector {0,number,\#}. Podr\u00EDa afectar a la identificaci\u00F3n y conversi\u00F3n. + #int previousSectorNumber,int currentSectorNumber INDEX_SECTOR_HEADER_NUM_BREAK=N\u00FAmero de encabezado del sector no continuo\: {0,number,\#} -> {1,number,\#} -#[DiscIndex.java] -# #int sectorNumber INDEX_MODE1_AMONG_MODE2=El sector {0,number,\#} es un Modo 1 encontrado entre sectores en Modo 2 @@ -663,7 +651,7 @@ INDEX_MODE1_AMONG_MODE2=El sector {0,number,\#} es un Modo 1 encontrado entre se #[DiscIndex.java] # #String lineFromIndexFile,ILocalizedMessage localizedErrorMessage -INDEX_PARSE_LINE_FAIL=No se ha podido analizar "{0}". Raz\u00F3n\: "{1}" +INDEX_PARSE_LINE_FAIL=No se ha podido analizar {0}. Raz\u00F3n\: {1} #[DiscIndex.java] # @@ -728,20 +716,25 @@ ITEM_TYPE_SOUND=clip de sonido #[DiscItem.java] ITEM_TYPE_SOUND_APPLY=clips de sonido -#[DiscItemCrusader.java] +#[DiscItemPacketBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration +GUI_PACKET_BASED_VID_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogramas, {3,number,\#} fps \= {4,time,m\:ss} + +#[DiscItemPacketBasedVideoStream.java] # -#int videoWidth,int videoHeight,int frameCount,int framesPerSecond,java.util.Date duration -GUI_CRUSADER_VID_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogramas, {3,number,\#} fps \= {4,time,m\:ss} +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration,int audioHz +GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO={0,number,\#}x{1,number,\#}, {2,number,\#} fotogramas, {3,number,\#} fps \= {4,time,m\:ss}, {5,number,\#} Hz #java.util.Date duration,int sampleRate GUI_SQUARE_AUDIO_DETAILS={0,time,m\:ss}, {1,number,\#} Hz, est\u00E9reo -#[DiscItemStrVideoStream.java] +#[DiscItemSectorBasedVideoStream.java] # #int videoWidth,int videoHeight,int frameCount,double doubleSpeedFramesPerSecond,java.util.Date doubleSpeedDuration,double singleSpeedFramesPerSecond,java.util.Date singleSpeedDuration GUI_STR_VIDEO_DETAILS_UNKNOWN_FPS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogramas, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} (o {5,number,\#.\#\#\#} fps \= {6,time,m\:ss}) -#[DiscItemStrVideoStream.java] +#[DiscItemSectorBasedVideoStream.java] # #int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration GUI_STR_VIDEO_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogramas, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} @@ -754,7 +747,7 @@ GUI_ISOFILE_DETAILS={0} bytes #[DiscItemISO9660File.java] # #int rawBytesPerSector -CMD_ISOFILE_ISO_HELP=-raw guardar {0,number,\#} bytes/sector (valor en bruto, el valor predeterminado es de 2048 bytes/sector) +CMD_ISOFILE_ISO_HELP=-raw guardar en bruto usando {0,number,\#} bytes/sector (el valor predeterminado es de 2048 bytes/sector) #[DiscItemISO9660File.java] CMD_ISOFILE_HELP_NO_OPTIONS=[no hay opciones disponibles] @@ -762,10 +755,10 @@ CMD_ISOFILE_HELP_NO_OPTIONS=[no hay opciones disponibles] #[DiscItemISO9660File.java] # #int rawBytesPerSector -CMD_ISOFILE_SAVING_RAW=Guardar en bruto ({0,number,\#} bytes/sector) +CMD_ISOFILE_SAVING_RAW=Guardar en bruto usando {0,number,\#} bytes/sector #[DiscItemISO9660File.java] -CMD_ISOFILE_SAVING_2048=Guardar de forma normal (2048 bytes/sector) +CMD_ISOFILE_SAVING_2048=Guardar de forma normal usando 2048 bytes/sector #[DiscItemISO9660File.java] GUI_ISOFILE_SAVE_2048=Normal (2048 bytes/sector) @@ -822,18 +815,16 @@ CMD_AUDIO_VOL=-vol <0-100> #int defaultVolumeLevel CMD_AUDIO_VOL_HELP=Ajustar volumen (predeterminado {0,number,\#}). -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidFormatName CMD_IGNORING_INVALID_FORMAT=Ignorando formato no v\u00E1lido {0} -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidVolume CMD_IGNORING_INVALID_VOLUME=Ignorando volumen no v\u00E1lido {0} -#[VideoSaverBuilder.java] -# #String badDiscSpeed CMD_IGNORING_INVALID_DISC_SPEED=Ignorando velocidad de disco no v\u00E1lida {0} @@ -970,57 +961,57 @@ QUALITY_PSX_DESCRIPTION=Emular (baja) calidad de PSX #[MdecDecodeQuality.java] QUALITY_PSX_COMMAND=psx -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION=Bic\u00FAbica -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_CMDLINE=Bic\u00FAbica -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_DESCRIPTION=Bell -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_CMDLINE=Bell #We shouldn't translate this string, as it doesn't really have a proper translation and nobody would be able to understand it! # -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION=Nearest Neighbor -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE=NearestNeighbor -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION=Lanczos3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_CMDLINE=Lanczos3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION=Mitchell -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_CMDLINE=Mitchell -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_DESCRIPTION=Hermite -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_CMDLINE=Hermite -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION=BSpline -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_CMDLINE=BSpline -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION=Bilineal -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_CMDLINE=Bilineal -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] # #ILocalizedMessage commandLineId,ILocalizedMessage interplationName CHROMA_UPSAMPLE_CMDLINE_HELP={0} ({1}) @@ -1065,10 +1056,12 @@ CMD_UNABLE_TO_IDENTIFY_FRAME_TYPE=No se ha podido identificar el tipo de fotogra #[ReplaceFrameFull.java, ReplaceFramePartial.java] # #String frameNumber,int maxSize -CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=\u00A1\u00A1No se ha podido comprimir el fotograma {0} de forma que ocupe {1,number,\#} bytes\!\! +CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=No se ha podido comprimir el fotograma {0} de forma que ocupe {1,number,\#} bytes #[ReplaceFramePartial.java] -CMD_NO_DIFFERENCE_SKIPPING=No se han encontrado diferencias, omitiendo. +# +#String frameNumber +CMD_NO_DIFFERENCE_SKIPPING=No se han encontrado diferencias en el fotograma {0}, omitiendo. #[ReplaceFramePartial.java] # @@ -1084,7 +1077,9 @@ CMD_ENTIRE_FRAME_DIFFERENT=Advertencia\: Todo el fotograma es distinto. REPLACE_UNABLE_READ_IMAGE=No se puede cargar {0} como una imagen. #[ReplaceFramePartial.java] -REPLACE_FRAME_DIMENSIONS_TOO_SMALL=Las dimensiones del fotograma sustituto son m\u00E1s peque\u00F1as que las del fotograma de origen. +# +#int newWidth,int newHeight,int existingWidth,int existingHeight +REPLACE_FRAME_DIMENSIONS_TOO_SMALL=Las dimensiones del fotograma sustituto ({0,number,\#}x{1,number,\#}) son m\u00E1s peque\u00F1as que las del fotograma de origen ({2,number,\#}x{3,number,\#}). #[ReplaceFrames.java] # @@ -1117,18 +1112,19 @@ REPLACE_FRAME_TYPE_NOT_IKI=El tipo de fotograma no es Iki. FRAME_NOT_IKI=El fotograma no est\u00E1 en el formato iki. -#[BitStreamUncompressor_Lain.java] FRAME_NOT_LAIN=El fotograma no est\u00E1 en el formato Lain. -#[BitStreamUncompressor_STRv1.java] FRAME_NOT_STRV1=El fotograma no est\u00E1 en el formato STRv1. -#[BitStreamUncompressor_STRv2.java] FRAME_NOT_STRV2=El fotograma no est\u00E1 en el formato STRv2. -#[BitStreamUncompressor_STRv3.java] FRAME_NOT_STRV3=El fotograma no est\u00E1 en el formato STRv3. +#[*] +# +#String frameFormatName +FRAME_IS_NOT_BITSTREAM_FORMAT=El fotograma no est\u00E1 en el formato {0}. + #[SectorIkiVideo.java] # #int sourceWidth,int sourceHeight,int replaceWidth,int replaceHeight @@ -1142,19 +1138,17 @@ UNEXPECTED_END_OF_AUDIO=Fin inesperado de los datos de audio #[BitStreamUncompressor_Iki.java] # #int macroBlockX,int macroBlockY,int quantizationScale -IKI_REDUCING_QSCALE_OF_MB_TO_VAL=Intentando reducir el Qscale de ({0,number,\#},{1,number,\#}) a {2,number,\#} +IKI_REDUCING_QSCALE_OF_MB_TO_VAL=Intentando reducir la escala de cuantificaci\u00F3n del macrobloque ({0,number,\#},{1,number,\#}) a {2,number,\#}. #We should try to translate all that's possible to translate, the closest of demux could be extraer. # -#[BitStreamUncompressor_Iki.java] -# #String frameNumber,int demuxSize,int sourceSize IKI_NEW_FRAME_GT_SRC_STOPPING=El tama\u00F1o del fotograma nuevo {0} al descomprimir ({1,number,\#}) es superior al tama\u00F1o m\u00E1ximo del origen {2,number,\#}, deteniendo #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java] # #String frameNumber,int demuxSize,int sourceSize -NEW_FRAME_FITS=El tama\u00F1o del fotograma nuevo {0} al descomprimir ({1,number,\#}) es igual o inferior al tama\u00F1o m\u00E1ximo del origen {2,number,\#} +NEW_FRAME_FITS=El tama\u00F1o del fotograma nuevo {0} ({1,number,\#}) entra en el tama\u00F1o disponible {2,number,\#}. #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_STRv2.java] # @@ -1166,17 +1160,17 @@ END_OF_STREAM=Fin del flujo de datos #[BitStreamUncompressor_Lain.java] # #int lumaQuantizationScale,int chromaQuantizationScale -TRYING_LUMA_CHROMA=Probando luma {0,number,\#} croma {1,number,\#} +TRYING_LUMA_CHROMA=Intentando comprimir usando la escala de cuantificaci\u00F3n de luminancia {0,number,\#} y la escala de cuantificaci\u00F3n de croma {1,number,\#}. #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java, ReplaceFrameFull.java] # #String frameNumber,int newFrameSize,int sourceFrameSize -NEW_FRAME_DOES_NOT_FIT=\u00A1\u00A1El tama\u00F1o del fotograma nuevo {0} al descomprimir ({1,number,\#}) es superior al tama\u00F1o m\u00E1ximo del origen {2,number,\#}\!\! +NEW_FRAME_DOES_NOT_FIT=El tama\u00F1o del fotograma nuevo {0} ({1,number,\#}) no entra en el tama\u00F1o disponible {2,number,\#}. #[BitStreamUncompressor_Lain.java] # #String frameNumber -COMPRESS_TOO_MUCH_ENERGY=El formato de v\u00EDdeo no admite el tama\u00F1o de los datos nuevos del fotograma {0} +COMPRESS_TOO_MUCH_ENERGY=El fotograma sustituto {0} tiene demasiados detalles y no puede ser comprimida correctamente. #int currentWidth,int newWidth INCONSISTENT_WIDTH=Ancho inconsistente {0,number,\#} \!\= {1,number,\#} @@ -1184,37 +1178,36 @@ INCONSISTENT_WIDTH=Ancho inconsistente {0,number,\#} \!\= {1,number,\#} #int currentHeight,int newHeight INCONSISTENT_HEIGHT=Altura inconsistente {0,number,\#} \!\= {1,number,\#} -#[VideoSaverBuilderCrusader.java] +#[PacketBasedVideoSaverBuilder.java] # #int audioSampleRate -EMBEDDED_CRUSADER_AUDIO_HZ=Audio de Crusader incrustado a {0,number,\#} Hz +CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ=Audio incrustado a {0,number,\#} Hz CRUSADER_VIDEO_CORRUPTED=V\u00EDdeo de Crusader\: No Remorse corrompido +#[DiscIndexerPolicenauts.java, SectorClaimToPolicenauts.java] +POLICENAUTS_DATA_CORRUPTION=Datos de Policenauts corrompidos + CRUSADER_AUDIO_CORRUPTED=Audio de Crusader\: No Remorse corrompido #String frameNumber,int chunkNumber MISSING_CHUNK=Falta el trozo {1,number,\#} del fotograma {0}. -#[SectorFrameBuilder.java] -# #int sectorNumber,int currentChunkCount,int newChunkCount DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO=Trozos del fotograma del sector {0,number,\#} cambiados de {1,number,\#} a {2,number,\#} -#[SectorFrameBuilder.java] -# #int sectorNumber,int chunkNumber,int chunksInFrame DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME=Trozo del sector {0,number,\#} n\u00FAmero {1,number,\#} >\= trozos en fotograma {2,number,\#} -#[SectorFrameBuilder.java] +#[SectorBasedFrameBuilder.java] # #int frameStartSector,int frameEndSector,int chunkNumber MISSING_CHUNK_FRAME_IN_SECTORS=El fotograma de los sectores {0,number,\#}-{1,number,\#} no contiene el trozo {2,number,\#} #[DemuxedCrusaderFrame.java, SectorBasedFrameReplace.java] -CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=\u00BFIntentas sustituir un fotograma con trozos incompletos? +CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=Intentando sustituir un fotograma corrompido. -#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java] +#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java, SectorBasedFrameBuilder.java] # #String frameNumber FRAME_NUM_CORRUPTED=Error en el fotograma {0}\: El fotograma est\u00E1 corrompido @@ -1241,12 +1234,12 @@ UNABLE_TO_DETERMINE_FRAME_TYPE=Error\: No se ha podido determinar el tipo de fot #[VDP.java] # #String frameNumber,int frameCount -FRAME_NUM_AHEAD_OF_READING=El fotograma {0} se ha adelantado a su lectura por {1,number,\#} {1,choice,1\#fotograma|2\#fotogramas}. +FRAME_NUM_AHEAD_OF_READING=El fotograma {0} del archivo de v\u00EDdeo aparecer\u00E1 {1,number,\#} {1,choice,1\#fotograma|2\#fotogramas} por delante de su momento original. #[VDP.java] # #int frameCount -FRAME_AHEAD_OF_READING=El fotograma se ha adelantado a su lectura por {0,number,\#} {0,choice,1\#fotograma|2\#fotogramas}. +FRAME_AHEAD_OF_READING=El fotograma del archivo de v\u00EDdeo aparecer\u00E1 {0,number,\#} {0,choice,1\#fotograma|2\#fotogramas} por delante de su momento original. #[VDP.java] # @@ -1270,7 +1263,12 @@ JPEG_ENCODER_FRAME_FAIL_NO_FRAME=El codificador JPEG sencillo de jPSXdec no pued #[VDP.java] # #int frameCount -WRITING_BLANK_FRAMES_TO_ALIGN_AV=Escribiendo {0,number,\#} fotograma(s) en blanco para sincronizar la reproducci\u00F3n de audio y v\u00EDdeo. +WRITING_BLANK_FRAMES_TO_ALIGN_AV=Escribiendo {0,number,\#} {0,choice,1\#fotograma|2\#fotogramas} en blanco para sincronizar la reproducci\u00F3n de audio y v\u00EDdeo. + +#[VDP.java] +# +#int frameCount +WRITING_DUP_FRAMES_TO_ALIGN_AV=Escribiendo {0,number,\#} {0,choice,1\#fotograma|2\#fotogramas} duplicados para sincronizar la reproducci\u00F3n de audio y v\u00EDdeo. #[VDP.java] # @@ -1373,7 +1371,7 @@ CMD_EMULATE_PSX_AV_SYNC_NY=Emular sincron\u00EDa audio/v\u00EDdeo de PSX\: {0,ch #[VideoSaverBuilder.java] CMD_NO_AUDIO=Sin sonido -#[VideoSaverBuilder.java] +#[VideoSaverBuilder.java, Command_Static.java] # #String upsampleDescription CMD_UPSAMPLE_QUALITY=Interpolaci\u00F3n de croma\: {0} @@ -1398,6 +1396,16 @@ CMD_FRAME_RANGE_AFTER=Omitir fotogramas posteriores a {0} #int willCrop CMD_CROPPING=Recorte\: {0,choice,0\#No|1\#S\u00ED} +#[VideoSaverBuilder.java] +# +#String videoFormatDescription +CMD_VIDEO_MUST_HAVE_EVEN_DIMS=Las dimensiones del v\u00EDdeo deben ser n\u00FAmeros pares para poder guardarlo como {0}, incrementando su tama\u00F1o en 1 p\u00EDxel. + +#[VideoSaverBuilder.java] +# +#int width,int height +CMD_DIMENSIONS=Dimensiones\: {0,number,\#}x{1,number,\#} + AVI_CLOSE_ERR=Error al cerrar el archivo AVI #[VideoSaverBuilder.java] @@ -1426,13 +1434,9 @@ CMD_VIDEO_UP_HELP=M\u00E9todo para interpolar el croma\n(por defecto {0}). Opcio #Was reading "Invalid decoding..." instead of "Invalid upsample." Fixing. # -#[VideoSaverBuilder.java] -# #String badQualityName CMD_UPSAMPLE_QUALITY_INVALID=Calidad de interpolaci\u00F3n no v\u00E1lida {0} -#[VideoSaverBuilder.java] -# #String badQualityName CMD_DECODE_QUALITY_INVALID=Calidad de decodificaci\u00F3n no v\u00E1lida {0} @@ -1450,13 +1454,11 @@ CMD_VIDEO_NOCROP=-nocrop #[VideoSaverBuilder.java] CMD_VIDEO_NOCROP_HELP=No recortar los bordes no utilizados en fotogramas. -#[VideoSaverBuilder.java] -# #String badFrameNumberType CMD_FRAME_NUMBER_TYPE_INVALID=Tipo {0} de n\u00FAmero de fotograma no v\u00E1lido #[VideoSaverBuilder.java] -CMD_VIDEO_FRAMES=-frame,-frames \# o \#-\# +CMD_VIDEO_FRAMES=-start \#, -end \# #[VideoSaverBuilder.java] CMD_VIDEO_FRAMES_HELP=Procesa solo los fotogramas en el rango. @@ -1469,8 +1471,6 @@ CMD_VIDEO_NUM=-num #ILocalizedMessage frameNumberType CMD_VIDEO_NUM_HELP=N\u00FAmero de fotogramas para usar al guardar la secuencia\nde im\u00E1genes (por defecto {0}). Opciones\: -#[VideoSaverBuilder.java] -# #String badFormatString CMD_VIDEO_FORMAT_INVALID=Formato de v\u00EDdeo no v\u00E1lido {0} @@ -1488,13 +1488,13 @@ CMD_VIDEO_HEADER_FRAME_NUMBER_UNSUPPORTED=El v\u00EDdeo no admite el indexado de #String badFrameNumberString CMD_FRAME_RANGE_INVALID=Fotograma(s) no v\u00E1lido(s) {0} -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD=-noaud -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD_HELP=No guardar el audio. -#[VideoSaverBuilderCrusaderGui.java] +#[PacketBasedVideoSaverBuilderGui.java] GUI_SAVE_AUDIO_LABEL=Guardar audio\: #[VideoSaverPanel.java] @@ -1543,16 +1543,16 @@ GUI_CROP_CHECKBOX=Recorte #[VideoSaverPanel.java] GUI_CHROMA_UPSAMPLING_LABEL=Interpolaci\u00F3n de croma\: -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV=-psxav -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV_HELP=Emular ritmo de audio/v\u00EDdeo de PSX. -#[VideoSaverBuilderStrGui.java] +#[SectorBasedVideoSaverBuilderGui.java] GUI_EMULATE_PSX_AV_SYNC_LABEL=Emular sincron\u00EDa audio/v\u00EDdeo de PSX\: -#[VideoSaverBuilderStrGui.java] +#[SectorBasedVideoSaverBuilderGui.java] GUI_VID_AUDIO_SAVE_ID_COLUMN= #[TimPaletteSelector.java] @@ -1568,13 +1568,9 @@ CMD_PALETTE_IMAGE_SAVE_FAIL=Imposible escribir imagen {0} con la paleta {1,numbe #String fileFormat CMD_TIM_SAVE_FORMAT=Formato\: {0} -#[TimSaverBuilder.java] -# #String badFileFormat CMD_TIM_SAVE_FORMAT_INVALID=Formato {0} no v\u00E1lido -#[TimSaverBuilder.java] -# #String badPaletteList CMD_TIM_PALETTE_LIST_INVALID=Lista de paletas no v\u00E1lida {0} @@ -1631,6 +1627,16 @@ TIM_INCOMPATIBLE=El formato del TIM "{0}" no coincide con el formato ya existent TIM_REPLACE_MULTI_CLUT_UNABLE=No se puede sustituir una imagen TIM multipaleta con una imagen sencilla. +#[*] +# +#String invalidValue,String command +CMD_INVALID_VALUE_FOR_CMD=Valor {0} no v\u00E1lido para {1}. + +#[*] +# +#String invalidValue,String command +CMD_IGNORING_INVALID_VALUE_FOR_CMD=Ignorando valor no v\u00E1lido {0} para {1}. + #[*] # #String fileName diff --git a/jpsxdec/src/jpsxdec/i18n/Translations_it.properties b/jpsxdec/src/jpsxdec/i18n/Translations_it.properties new file mode 100644 index 0000000..b8eda6f --- /dev/null +++ b/jpsxdec/src/jpsxdec/i18n/Translations_it.properties @@ -0,0 +1,1686 @@ +# jPSXdec Translations +# Copyright (c) 2015-2019 +# Michael Sabin, Víctor González, Sergi Medina, Gianluigi "Infrid" Cusimano +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#[CommandLine.java, DebugFormatter.java, Gui.java, GuiSettings.java, Mdec2Jpeg.java, UserFriendlyLogger.java] +# +#String versionNumber +JPSXDEC_VERSION_NON_COMMERCIAL=jPSXdec\: PSX media decoder (software non commerciale) v{0} + +#[Command.java] +# +#java.io.File sourceFileName +CMD_USING_SRC_FILE=Lettura file sorgente {0} + +#[Command.java] +CMD_NEED_INPUT_OR_INDEX=Dovresti indicare un file da analizzare e/o un indice da caricare. + +#[Command.java] +CMD_DISC_FILE_REQUIRED=Per questo comando \u00E8 necessario fornire una immagine del disco. + +#[Command.java] +CMD_INPUT_FILE_REQUIRED=File input richiesto per questo comando. + +#[Command.java] +# +#int itemCount +CMD_ITEMS_LOADED={0,number,\#} file caricati. + +#[Command.java] +# +#String fileName +CMD_READING_INDEX_FILE=Lettura indice {0} + +#[Command.java] +# +#java.io.File fileName +CMD_INPUT_FILE_NOT_FOUND=File non trovato {0} + +#String badItemNumber +CMD_ITEM_NUMBER_INVALID=Numero voce non valido\: {0} + +#String badItemIdentifier +CMD_ITEM_ID_INVALID=Identificativo voce non valido\: {0} + +#da rivedere +# +#[Command_Items.java] +# +#String discItemType +CMD_NO_ITEMS_OF_TYPE=Spiacente, non \u00E8 stato possibile trovare alcuna voce sul disco di tipo {0} + +CMD_DISC_ITEM_NOT_AUDIO_VIDEO_NO_PLAYER=Impossibile riprodurre la voce, non sembra essere un audio o video. + +#[Command_Items.java] +CMD_DISC_ITEM_NOT_VIDEO=La voce non \u00E8 un video + +#[Command_Items.java] +CMD_DISC_ITEM_NOT_XA=La voce non \u00E8 una traccia XA. + +#[Command_Items.java] +CMD_DISC_ITEM_NOT_TIM=La voce non \u00E8 una immagine TIM. + +#[Command_Items.java] +# +#int discItemIndex +CMD_DISC_ITEM_NOT_FOUND_NUM=Impossibile trovare la voce {0,number,\#} + +#[Command_Items.java] +# +#String discItemId +CMD_DISC_ITEM_NOT_FOUND_STR=Impossibile trovare la voce {0} + +#[Command_Items.java] +CMD_DETAILED_HELP_FOR=Informazioni dettagliate per + +#[Command_Items.java] +# +#String discItemDescription +CMD_SAVING=Salvataggio in corso per {0} + +#[Command_Items.java] +CMD_ITEM_COMPLETE=File completato. + +#[Command_Items.java] +CMD_ALL_ITEMS_COMPLETE=Tutti le voci nell'indice sono stati processate. + +#[Command_Items.java] +CMD_PROCESS_COMPLETE=Decodifica/estrazione del disco completata. + +#[Command_Items.java, DiscIndex.java] +# +#double durationInSeconds +PROCESS_TIME=Tempo\: {0,number,\#.\#\#} s + +#[Command_Items.java, Command_Static.java] +# +#int fileCount +CMD_NUM_FILES_CREATED={0,choice,0\#Nessun file creato|1\#1 un file creato|2\#'{0,number,\#}' file sono stati creati} + +#[Command_Items.java] +CMD_REOPENING_DISC_WRITE_ACCESS=Riapertura immagine disco in scrittura. + +#[Command_Items.java] +CMD_BACKUP_DISC_IMAGE_WARNING=Assicurati di avere una copia di backup, questa operazione \u00E8 irreversibile. + +#[Command_Items.java] +# +#String badItemNumber +CMD_XA_REPLACE_BAD_ITEM_NUM=Numero voce {0} XA non valido o mancante. + +CMD_CREATING_PLAYER=Creazione player per + +CMD_PLAYER_ERR=Errore di riproduzione + +#[Command_Items.java] +# +#String patchIndexFileName +CMD_XA_REPLACE_OPENING_PATCH_IDX=Apertura indice patch {0} + +#[Command_Items.java, SavingGuiTask.java] +SAVE_LOG_FILE_BASE_NAME=salvataggi + +#[Command.java, CommandLine.java, Gui.java] +INDEX_LOG_FILE_BASE_NAME=indici + +#[Command_Items.java] +REPLACE_LOG_FILE_BASE_NAME=sostituzioni + +#[UserFriendlyLogger.java] +# +#String logLevel,String logMessage +USER_LOG_MESSAGE=[{0}] {1} + +#String logLevel,String logMessage,String exceptionName +USER_LOG_MESSAGE_EXCEPTION=[{0}] {1} {2} + +#String logLevel,String logMessage,String exceptionName,String exceptionMessage +USER_LOG_MESSAGE_EXCEPTION_MSG=[{0}] {1} {2} \: {3} + +#String logLevel,String exceptionName +USER_LOG_EXCEPTION=[{0}] {1} + +#String logLevel,String exceptionName,String exceptionMessage +USER_LOG_EXCEPTION_MSG=[{0}] {1} \: {2} + +#String badSectorRangeString +CMD_SECTOR_RANGE_INVALID=Intervallo settori non valido\: {0} + +#[Command_CopySect.java] +# +#int startSector,int endSector,String destinationFile +CMD_COPYING_SECTOR=Copia settori da {0,number,\#} - {1,number,\#} a {2} in corso + +#[Command_SectorDump.java] +CMD_GENERATING_SECTOR_LIST=Generazione lista settori + +#[Command_Static.java] +CMD_DIM_OPTION_REQURIED=opzione -dim richiesta + +#String badDimensionsString +CMD_INVALID_DIMENSIONS=Dimensioni non valice\: {0} + +#String badQuality +CMD_QUALITY_INVALID=Livello di qualit\u00E0 non valido {0} + +#[Command_Static.java] +# +#ILocalizedMessage qualityName +CMD_USING_QUALITY=Qualit\u00E0 selezionata {0} + +#[Command_Static.java] +CMD_NOT_TIM=Errore\: non sembra essere una immagine TIM + +#ILocalizedMessage upsampleDescription +CMD_USING_UPSAMPLING=Uso l''interpolazione {0} + +#[Command_Static.java] +CMD_TIM_IO_ERR=Errore di lettura/scrittura file TIM + +#String badFormat +CMD_FORMAT_INVALID=Formato non valido {0} + +#[Command_Static.java] +CMD_ASSERT_DISABLED_NO_DEBUG=Impossibile abilitare la decodifica debug per via delle asserzioni disabilitate. + +#[Command_Static.java] +CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA=Avvia java usando gli switch -ea + +#[Command_Static.java] +# +#java.io.File timFileName +CMD_READING_TIM=Lettura file TIM {0} + +#String badStaticTypeName +CMD_STATIC_TYPE_INVALID=Tipo statico non valido\: {0} + +#[Command_Static.java] +CMD_FRAME_CONVERT_OK=Fotogramma convertito correttamente. + +#[Command_Static.java] +# +#java.io.File fileName +CMD_READING_STATIC_FILE=Lettura file statico {0} + +#[Command_Static.java] +CMD_IMAGE_CONVERT_OK=Immagine convertita correttamente + +#[Command_Static.java, VideoSaverBuilder.java] +# +#java.io.File fileName +CMD_SAVING_AS=Salvataggio in corso\: {0} + +#[Command_Visualize.java] +CMD_GENERATING_VISUALIZATION=Generazione visualizzazione + +#[Command_Visualize.java] +CMD_VISUALIZATION_ERR=Errore durante la lettura/scritta della visualizzazione + +#[Command.java] +# +#ILocalizedMessage localizedDetails +ERR_LOADING_INDEX_FILE_REASON=Errore caricamento file indice\: {0} + +#[CommandLine.java] +# +#int itemCount +CMD_NUM_ITEMS_FOUND={0,number,\#} file trovati + +#[CommandLine.java] +CMD_BUILDING_INDEX=Creazione indice + +CMD_DISC_READ_ERROR=Errore lettura disco. + +#String badVerbosityLevel +CMD_VERBOSE_LVL_INVALID_STR=Livello verbosit\u00E0 non valido {0} + +#int badVerbosityNumber +CMD_VERBOSE_LVL_INVALID_NUM=Livello di verbosit\u00E0 non valido {0,number,\#} + +#[CommandLine.java] +# +#String fileName +CMD_SAVING_INDEX=Salvataggio indice come {0} + +#[CommandLine.java] +CMD_TRY_HELP=Prova -? per aiuto. + +#[CommandLine.java] +CMD_NOT_SAVING_EMPTY_INDEX=Nessun voce trovata, inutile creare un indice + +#[CommandLine.java, Command_Items.java] +# +#java.lang.Throwable errorMessage,String exceptionType +CMD_ERR_EX_CLASS=ERRORE\: {0} ({1}) + +#[CommandLine.java] +CMD_NEED_MAIN_COMMAND=Necessito un comando principale. + +#[CommandLine.java] +# +#ILocalizedMessage discFormatDescription +CMD_DISC_IDENTIFIED=Identificato come {0} + +#[CommandLine.java] +CMD_COMMAND_NEEDS_DISC=Il comando ha bisogno di un file disco + +#[CommandLine.java] +CMD_TOO_MANY_MAIN_COMMANDS=Troppi comandi principali. + +#String fileName +CMD_FILE_NOT_FOUND_FILE=File non trovato {0} + +#[CommandLine.java, IndexingGui.java] +# +#String cdFileDescription +CMD_GUI_INDEXING=Indicizzando {0} + +#[ConsoleProgressLogger.java] +# +#String progressBar,double percentComplete,int warningCount,int errorCount +CMD_PROGRESS=[{0}] {1,number,\#%}{2,choice,0\#|1\# '{2,number,\#}' '{2,choice,0\#avvertimenti|1\#avvertimento|1 {1,number,\#} + +#int sectorNumber +INDEX_MODE1_AMONG_MODE2=Il settore {0,number,\#} Mode 1 \u00E8 stato trovato insieme a settori Mode 2 + +#[DiscIndex.java] +# +#String lineFromIndexFile,ILocalizedMessage localizedErrorMessage +INDEX_PARSE_LINE_FAIL=Impossibile interpretare "{0}" per via di "{1}" + +#[DiscIndex.java] +# +#String lineFromIndexFile +INDEX_UNHANDLED_LINE=Linea non rinosciuta {0} + +#[DiscIndex.java] +# +#String discFileIdentifier +INDEX_MULTIPLE_CD=L''indice contiene molte righe che iniziano per "{0}" + +#[DiscIndex.java] +# +#String discFileIdentifier +INDEX_NO_CD=L''indice manca di una riga che inizia per "{0}", nessun file sorgente \u00E8 stato fornito. + +#[DiscIndex.java] +INDEX_INCONSTSTENCIES=Trovate inconsistenze nell'indice, per caso \u00E8 stato modificato? + +#[DiscIndex.java] +# +#int currentSectorNumber,int totalSectorCount,int itemsFound +INDEX_SECTOR_ITEM_PROGRESS=Settore {0,number,\#} / {1,number,\#} {2,number,\#} voci trovate + +#[DiscIndex.java] +# +#String lineCommentCharacter +INDEX_COMMENT={0} Le righe che iniziano per {0} verranno ignorate + +#[DiscIndexerXaAudio.java] +# +#int sectorNumber,int channelNumber +IGNORING_SILENT_XA_SECTOR=Ignoro la traccia audio XA muta che dura per un intero settore {0,number,\#}, canale {1,number,\#} + +#[DiscItem.java] +ITEM_TYPE_VIDEO=Video + +#[DiscItem.java] +ITEM_TYPE_VIDEO_APPLY=Video + +#[DiscItem.java] +ITEM_TYPE_FILE=File + +#[DiscItem.java] +ITEM_TYPE_FILE_APPLY=File (tutti) + +#[DiscItem.java] +ITEM_TYPE_AUDIO=Audio + +#[DiscItem.java] +ITEM_TYPE_AUDIO_APPLY=Audio (tutti) + +#[DiscItem.java] +ITEM_TYPE_IMAGE=Immagine + +#[DiscItem.java] +ITEM_TYPE_IMAGE_APPLY=Immagini + +#[DiscItem.java] +ITEM_TYPE_SOUND=Clip audio + +#[DiscItem.java] +ITEM_TYPE_SOUND_APPLY=Clip audio (tutti) + +#[DiscItemPacketBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration +GUI_PACKET_BASED_VID_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogrammi, {3,number,\#} fps \= {4,time,m\:ss} + +#[DiscItemPacketBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration,int audioHz +GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO={0,number,\#}x{1,number,\#}, {2,number,\#} fotogrammi, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss}, {5,number,\#} Hz + +#java.util.Date duration,int sampleRate +GUI_SQUARE_AUDIO_DETAILS={0,time,m\:ss}, {1,number,\#} Hz Stereo + +#[DiscItemSectorBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double doubleSpeedFramesPerSecond,java.util.Date doubleSpeedDuration,double singleSpeedFramesPerSecond,java.util.Date singleSpeedDuration +GUI_STR_VIDEO_DETAILS_UNKNOWN_FPS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogrammi, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} (or {5,number,\#.\#\#\#} fps \= {6,time,m\:ss}) + +#[DiscItemSectorBasedVideoStream.java] +# +#int videoWidth,int videoHeight,int frameCount,double framesPerSecond,java.util.Date duration +GUI_STR_VIDEO_DETAILS={0,number,\#}x{1,number,\#}, {2,number,\#} fotogrammi, {3,number,\#.\#\#\#} fps \= {4,time,m\:ss} + +#[DiscItemISO9660File.java] +# +#long fileSize +GUI_ISOFILE_DETAILS={0} byte + +#[DiscItemISO9660File.java] +# +#int rawBytesPerSector +CMD_ISOFILE_ISO_HELP=-raw salva come dati grezzi {0,number,\#} byte/settore (2048 \u00E8 il valore predefinito) + +#[DiscItemISO9660File.java] +CMD_ISOFILE_HELP_NO_OPTIONS=[nessuna opzione disponibile] + +#[DiscItemISO9660File.java] +# +#int rawBytesPerSector +CMD_ISOFILE_SAVING_RAW=Salvataggio raw {0,number,\#} byte/settore + +#[DiscItemISO9660File.java] +CMD_ISOFILE_SAVING_2048=Salvataggio col normale 2048 byte/settore + +#[DiscItemISO9660File.java] +GUI_ISOFILE_SAVE_2048=Normale 2048 byte/settore + +#[DiscItemISO9660File.java] +GUI_ISOFILE_SAVE_RAW=Raw + +#[DiscItemISO9660File.java] +# +#int rawSectorSize +GUI_ISOFILE_SAVE_RAW_SIZE=Raw {0,number,\#} byte/settore + +#[DiscIndex.java, DiscIndexerISO9660.java, DiscItemISO9660File.java] +# +#String itemDescription +NOT_CONTAINED_IN_DISC={0} non \u00E8 completamente dentro i limiti del formato CD/file, l''estrazione comportet\u00E0 errori. + +#[DiscIndexerISO9660.java] +# +#String fileName +ISO_FILE_CORRUPTED_IGNORING=Le informazioni del file disco {0} sono corrotte, ignoro + +#[DiscItemISO9660File.java, AudioSaverBuilderGui.java, SpuSaverBuilderGui.java, TimSaverBuilderGui.java, VideoSaverPanel.java] +GUI_SAVE_AS_LABEL=Salva come\: + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +# +#ILocalizedMessage audioFormat +CMD_AUDIO_FORMAT=Formato\: {0} + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +# +#double volumeLevelPercent +CMD_VOLUME_PERCENT=Volume\: {0,number,\#%}% + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +# +#java.io.File fileName +CMD_FILENAME=Nome file\: {0} + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +CMD_AUDIO_AF=-audfmt,-af + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +# +#String defaultAudioFormatName +CMD_AUDIO_AF_HELP=Formato output audio (default {0}). Opzioni\: + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +CMD_AUDIO_VOL=-vol <0-100> + +#[AudioSaverBuilder.java, SpuSaverBuilder.java] +# +#int defaultVolumeLevel +CMD_AUDIO_VOL_HELP=Regola volume (default {0,number,\#}). + +#[SpuSaverBuilder.java] +# +#String invalidFormatName +CMD_IGNORING_INVALID_FORMAT=Ignoro formato non valido {0} + +#[SpuSaverBuilder.java] +# +#String invalidVolume +CMD_IGNORING_INVALID_VOLUME=Ignoro valore volume non valido {0} + +#String badDiscSpeed +CMD_IGNORING_INVALID_DISC_SPEED=Ignoro velocit\u00E0 di lettura non valida {0} + +GUI_VOLUME_LABEL=Volume\: + +AVI_FILE_IS_CLOSED=File AVI chiuso + +#[DiscItemXaAudioStream.java, SquareAudioSectorPair.java] +# +#long startOfSamples,String sectorDescription +WRITING_SAMPLES_TO_SECTOR=Scrittura campioni da {0,number,\#} al settore {1} + +#[DiscItemSquareAudioStream.java, DiscItemXaAudioStream.java] +# +#int sectorNumber +CMD_PATCHING_SECTOR_NUMBER=Applico la patch al settore {0,number,\#} + +#[DiscItemXaAudioStream.java] +# +#String sectorDescription +CMD_PATCHING_SECTOR_DESCRIPTION=Applico la patch al settore {0} + +#[DiscItemXaAudioStream.java] +# +#String otherSectorDescription +CMD_PATCHING_WITH_SECTOR_DESCRIPTION=con questo settore {0} + +#[DiscItemXaAudioStream.java] +# +#String fieldName,int badFieldNumberValue +FIELD_HAS_INVALID_VALUE_NUM=il campo {0} ha un valore non valido\: {1,number,\#} + +#[DiscItemXaAudioStream.java, SerializedDiscItem.java] +# +#String fieldName,String badFieldStringValue +FIELD_HAS_INVALID_VALUE_STR=Il campo {0} ha un valore non valido\: {1} + +#[SectorXaAudioToAudioPacket.java] +# +#int sectorNumber,long firstBadSample +XA_AUDIO_CORRUPTED=audio XA corrotto al settore {0,number,\#}, i campionamenti dopo {1,number,\#} saranno afflitti + +#[CrusaderPacketToFrameAndAudio.java, SquareAudioSectorPairToAudioPacket.java] +# +#int approximateSectorNumber,long firstBadSample +SPU_ADPCM_CORRUPTED=Audio corrotto vicino al settore {0,number,\#}, i campionamenti dopo {1,number,\#} saranno afflitti + +#[DiscItemSquareAudioStream.java, DiscItemXaAudioStream.java] +# +#java.util.Date duration,int audioSampleRate,int audioChannelCount +GUI_AUDIO_DESCRIPTION={0,time,m\:ss}, {1,number,\#} Hz {2,choice,1\#Mono|2\#Stereo} + +#[DiscItemXaAudioStream.java] +# +#String discItemDescription +CMD_PATCHING_DISC_ITEM=Applico la patch {0} + +#[DiscItemXaAudioStream.java] +# +#String otherDiscItemDescription +CMD_PATCHING_WITH_DISC_ITEM=con {0} + +#[DiscItemXaAudioStream.java] +# +#int newBitsPerSample,long newSampleCount,int newChannelCount,int newSamplesPerSecond,int existingBitsPerSample,long existingSampleCount,int existingChannelCount,int existingSamplesPerSecond +XA_REPLACE_FORMAT_MISMATCH=Discrepanza audio XA\: nuova traccia audio XA ({0,number,\#} bit/campione, {1,number} {2,choice,1\#Mono|2\#Stereo} frequenza {3,number}Hz) non corrisponde alla traccia esistente ({4,number,\#} bit/campione, {5,number} {6,choice,1\#Mono|2\#Stereo} frequenza {7,number}Hz) + +#[DiscItemSquareAudioStream.java, DiscItemXaAudioStream.java] +# +#long newSampleCount,int newChannelCount,float newSampleRate,long existingSamleCount,int existingChannelCount,int existingSampleRate +AUDIO_REPLACE_FORMAT_MISMATCH=Discrepanza audio\: nuovo audio ({0,number,\#} {1,choice,1\#Mono|2\#Stereo} campionato a {2,number,\#.\#}Hz) non corrisponde all''audio esistente ({3,number,\#} {4,choice,1\#Mono|2\#Stereo} campionamento a {5,number,\#.\#}Hz) + +#float incompatibleAudioSampleRate,int xaAudioSampleRate +XA_COPY_REPLACE_SAMPLE_RATE_MISMATCH=La frequenza di campionamneto {0} non corrisponde a quella della traccia XA {1} + +#int replaceChannelCount,int sourceChannelCount +XA_COPY_REPLACE_CHANNEL_MISMATCH=Il file audio \u00E8 {0,choice,1\#Mono|2\#Stereo} mentre la traccia XA ha invece {1,choice,1\#Mono|2\#Stereo} + +XA_ENCODE_REPLACE_SRC_AUDIO_EXHAUSTED=La traccia audio \u00E8 terminata, continuo usando il silenzio. + +#[DiscItemXaAudioStream.java] +XA_COPY_REPLACE_SRC_XA_EXHAUSTED=Fine del file XA di origine, completamento in corso + +#[FrameLookup.java, FrameNumberNumber.java] +# +#String badFrameNumberString +FRAME_NUM_INVALID=Numero fotogramma non valido {0} + +#[FrameNumber.java] +FRAME_NUM_FORMAT_INDEX=Indice + +#[FrameNumber.java] +FRAME_NUM_FORMAT_SECTOR=Settore + +#[FrameNumber.java] +FRAME_NUM_FORMAT_HEADER=Header + +#[FrameNumber.java] +# +#String formattedSectorNumber +FRAME_NUM_FORMATTER_SECTOR=Settore {0} + +#[FrameNumber.java] +# +#String formattedFrameNumber +FRAME_NUM_FORMATTER_FRAME=Fotogramma {0} + +#String badFrameNumberFormat +FRAME_NUM_FORMAT_INVALID=Formato numero frame non valido {0} + +#[VideoSaver.java] +# +#int frameNumber +FRAME_MISSING_FRAME_NUMBER_HEADER=Il fotogramma {0,number,\#} manca di informazioni riguardo il numero + +#[HeaderFrameNumber.java, IndexSectorFrameNumber.java, VideoFileNameFormatter.java] +FRAMES_UNEXPECTED_NUMBER=Sono stati trovati un numero inaspettato di fotogrammi, questi potrebbero essere salvati in ordine incostitente. + +#[MdecDecodeQuality.java] +QUALITY_HIGH_DESCRIPTION=Alta qualit\u00E0 (pi\u00F9 lento) + +#[MdecDecodeQuality.java] +QUALITY_HIGH_COMMAND=alta + +#[MdecDecodeQuality.java] +QUALITY_FAST_DESCRIPTION=Veloce (bassa qualit\u00E0) + +#[MdecDecodeQuality.java] +QUALITY_FAST_COMMAND=bassa + +#[MdecDecodeQuality.java] +QUALITY_PSX_DESCRIPTION=Emula la qualit\u00E0 della PSX (bassa) + +#[MdecDecodeQuality.java] +QUALITY_PSX_COMMAND=psx + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION=Bicubico + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BICUBIC_CMDLINE=Bicubico + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BELL_DESCRIPTION=Gaussiana + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BELL_CMDLINE=Gaussiana + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION=Nearest Neighbor + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE=NearestNeighbor + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION=Lanczos3 + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_LANCZOS3_CMDLINE=Lanczos3 + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION=Mitchell + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_MITCHELL_CMDLINE=Mitchell + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_HERMITE_DESCRIPTION=Hermite + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_HERMITE_CMDLINE=Hermite + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION=BSpline + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BSPLINE_CMDLINE=BSpline + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION=Bilineare + +#[ChromaUpsample.java] +CHROMA_UPSAMPLE_BILINEAR_CMDLINE=Bilineare + +#[ChromaUpsample.java] +# +#ILocalizedMessage commandLineId,ILocalizedMessage interplationName +CHROMA_UPSAMPLE_CMDLINE_HELP={0} ({1}) + +#[ReplaceFrameFull.java] +# +#String imageFile,int sourceWidth,int sourceHeight,int replaceWidth,int replaceHeight +REPLACE_FRAME_DIMENSIONS_MISMATCH=Il fotogramma di rimpiazzo {0} ha una risoluzione di {1,number,\#}x{2,number,\#} e non combacia con quello di destinazione {3,number,\#}x{4,number,\#} + +#[ReplaceFrameFull.java] +# +#java.io.File bitstreamFile +REPLACE_BITSTREAM_MISMATCH=Il tipo di fotogramma del file {0} non corrisponde a quello esistente + +#[ReplaceFrameFull.java] +# +#String mdecFileName,String frameNumber +REPLACE_INCOMPATIBLE_MDEC=File mdec {0} non compatibile per il fotogramma {1} + +#[ReplaceFrameFull.java] +# +#String mdecFileName,String frameNumber +REPLACE_INCOMPLETE_MDEC=File mdec {0} non completo per il fotogramma {1} + +#[ReplaceFrameFull.java] +# +#String mdecFileName,String frameNumber +REPLACE_CORRUPTED_MDEC=File mdec {0} corrotto per il fotogramma {1} + +#[ReplaceFrameFull.java] +# +#String badFormatName +REPLACE_INVALID_IMAGE_FORMAT=Formato file dell''immagine di rimpiazzo non valido {0} + +#[ReplaceFrameFull.java] +# +#java.io.File fileName +REPLACE_FILE_NOT_JAVA_IMAGE=Impossibile leggere {0} come una immagine. Per caso hai dimenticato di specificare il formato nelle opzioni del file XML? + +CMD_UNABLE_TO_IDENTIFY_FRAME_TYPE=Impossibile identificare il tipo di fotogramma + +#[ReplaceFrameFull.java, ReplaceFramePartial.java] +# +#String frameNumber,int maxSize +CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=Impossibile comprimere oltre il fotogramma {0} per farlo stare entro {1,number,\#} byte\! + +#[ReplaceFramePartial.java] +# +#String frameNumber +CMD_NO_DIFFERENCE_SKIPPING=Nessuna differenza trovata {0}, salto. + +#[ReplaceFramePartial.java] +# +#int differenceCount +CMD_REPLACE_FOUND_DIFFERENT_MACRO_BLOCKS=Trovato un numero differente di macroblocchi {0,number,\#} invece che 16x16 + +#[ReplaceFramePartial.java] +CMD_ENTIRE_FRAME_DIFFERENT=Attenzione\: L'intero fotogramma \u00E8 differente + +#[ReplaceFramePartial.java] +# +#String fileName +REPLACE_UNABLE_READ_IMAGE=Impossibile caricare {0} come immagine + +#[ReplaceFramePartial.java] +# +#int newWidth,int newHeight,int existingWidth,int existingHeight +REPLACE_FRAME_DIMENSIONS_TOO_SMALL=La dimensione del fotogramma di rimpiazzo {0,number,\#}x{1,number,\#} \u00E8 pi\u00F9 piccola di quella della sorgente {2,number,\#}x{3,number,\#} + +#[ReplaceFrames.java] +# +#String frameNumber,java.io.File fileName +CMD_REPLACING_FRAME_WITH_FILE=Sostizione fotogramma {0} con {1} + +#[ReplaceFrames.java] +# +#String xmlErrorInEnglish +REPLACE_FRAME_XML_ERROR=Errore sul file XML del fotogramma di rimpiazzo\: {0} + +#[ReplaceFrames.java] +# +#String xmlRootNodeName +CMD_REPLACE_XML_INVALID_ROOT_NODE=Elemento radice non valido {0} + +#[ReplaceFrames.java] +# +#String versionNumber +CMD_REPLACE_XML_INVALID_VERSION=Versione non valida {0} + +#[SectorStrVideo.java] +REPLACE_FRAME_TYPE_NOT_V2_V3=Il tipo di fotogramma non \u00E8 SRTv2 o STRv3 + +#[SectorFF9.java] +REPLACE_FRAME_TYPE_NOT_V2=Il tipo di fotogramma non \u00E8 STRv2 + +#[SectorIkiVideo.java] +REPLACE_FRAME_TYPE_NOT_IKI=Il tipo di fotogramma non \u00E8 Iki + +FRAME_NOT_IKI=Il fotogramma non \u00E8 in formato Iki + +FRAME_NOT_LAIN=Il fotogramma non in formato Lain + +FRAME_NOT_STRV1=Il fotogramma non in formato STRv1 + +FRAME_NOT_STRV2=Il fotogramma non in formato STRv2 + +FRAME_NOT_STRV3=Il fotogramma non in formato STRv3 + +#[*] +# +#String frameFormatName +FRAME_IS_NOT_BITSTREAM_FORMAT=Il fotogramma non \u00E8 in formato {0} + +#[SectorIkiVideo.java] +# +#int sourceWidth,int sourceHeight,int replaceWidth,int replaceHeight +REPLACE_FRAME_IKI_DIMENSIONS_MISMATCH=Dimensioni fotogramma iki non corrisponde alla dimensione del settore\: {0,number,\#}x{1,number,\#} \!\= {2,number,\#}x{3,number,\#} + +#[SectorLainVideo.java] +REPLACE_FRAME_TYPE_NOT_LAIN=Dati incompatibili con Lain + +UNEXPECTED_END_OF_AUDIO=Inaspettata fine dei dati audio + +#[BitStreamUncompressor_Iki.java] +# +#int macroBlockX,int macroBlockY,int quantizationScale +IKI_REDUCING_QSCALE_OF_MB_TO_VAL=Provo a ridurre la scala dei macroblocchi da ({0,number,\#},{1,number,\#}) a {2,number,\#} + +#String frameNumber,int demuxSize,int sourceSize +IKI_NEW_FRAME_GT_SRC_STOPPING=Nuovo fotogramma {0} demux size {1,number,\#} maggiore del max source {2,number,\#}, interruzione. + +#[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java] +# +#String frameNumber,int demuxSize,int sourceSize +NEW_FRAME_FITS=La dimensione {1,number,\#} del nuovo fotogramma {0} entra nello spazio disponibile {2,number,\#}. + +#[BitStreamUncompressor_Iki.java, BitStreamUncompressor_STRv2.java] +# +#int quantizationScale +TRYING_QSCALE=Provo {0,number,\#} + +END_OF_STREAM=Fine dello stream dati + +#[BitStreamUncompressor_Lain.java] +# +#int lumaQuantizationScale,int chromaQuantizationScale +TRYING_LUMA_CHROMA=Provo a comprimere la luminanza con un fattore di quantizzazione {0,number,\#} e crominanza {1,number,\#} + +#[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java, ReplaceFrameFull.java] +# +#String frameNumber,int newFrameSize,int sourceFrameSize +NEW_FRAME_DOES_NOT_FIT=La dimensione {1,number,\#} del nuovo fotogramma {0} NON entra nello spazio disponibile {2,number,\#}. + +#[BitStreamUncompressor_Lain.java] +# +#String frameNumber +COMPRESS_TOO_MUCH_ENERGY=Il formato video non pu\u00F2 gestire la complessit\u00E0 dei nuovi dati per il frame {0} + +#int currentWidth,int newWidth +INCONSISTENT_WIDTH=Larghezza discrepante {0,number,\#} \u00E8 diverso da {1,number,\#} + +#int currentHeight,int newHeight +INCONSISTENT_HEIGHT=Altezza discrepante\: {0,number,\#} \u00E8 diverso da {1,number,\#} + +#[PacketBasedVideoSaverBuilder.java] +# +#int audioSampleRate +CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ=Audio Embedded {0,number,\#} Hz + +CRUSADER_VIDEO_CORRUPTED=Video corrotto per "Crusader\: No Remorse" + +#[DiscIndexerPolicenauts.java, SectorClaimToPolicenauts.java] +POLICENAUTS_DATA_CORRUPTION=Corruzione dati per Policenauts + +CRUSADER_AUDIO_CORRUPTED=Audio corrotto per "Crusader\: No Remorse" + +#String frameNumber,int chunkNumber +MISSING_CHUNK=Al fotogramma {0} manca la parte {1,number,\#}. + +#int sectorNumber,int currentChunkCount,int newChunkCount +DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO=Sector {0,number,\#} chunks in frame changed from {1,number,\#} to {2,number,\#} + +#int sectorNumber,int chunkNumber,int chunksInFrame +DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME=Sector {0,number,\#} chunk number {1,number,\#} >\= chunks in frame {2,number,\#} + +#[SectorBasedFrameBuilder.java] +# +#int frameStartSector,int frameEndSector,int chunkNumber +MISSING_CHUNK_FRAME_IN_SECTORS=Fotogramma in settori {0,number,\#}-{1,number,\#} manca di un pezzo {2,number,\#} + +#[DemuxedCrusaderFrame.java, SectorBasedFrameReplace.java] +CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=Provo a rimpiazzare un fotogramma corrotto esistente + +#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java, SectorBasedFrameBuilder.java] +# +#String frameNumber +FRAME_NUM_CORRUPTED=Errore col fotogramma {0}\: Pare sia corrotto + +#[VDP.java] +FRAME_CORRUPTED=Errore\: fotogramma corrotto + +#[ReplaceFrameFull.java, ReplaceFramePartial.java, VDP.java] +# +#String frameNumber +FRAME_NUM_INCOMPLETE=Errore col fotogramma {0}\: Pare sia incompleto + +#[VDP.java] +FRAME_INCOMPLETE=Erroe\: Fotogramma incompleto + +#[ReplaceFrameFull.java, ReplaceFramePartial.java, VDP.java] +# +#String frameNumber +UNABLE_TO_DETERMINE_FRAME_TYPE_FRM=Errore col fotogramma {0}\: Impossibile determinare il tipo. + +#[VDP.java] +UNABLE_TO_DETERMINE_FRAME_TYPE=Errore\: Impossibile determinare il tipo di fotogramma. + +#[VDP.java] +# +#String frameNumber,int frameCount +FRAME_NUM_AHEAD_OF_READING=Fotogramma {0} \u00E8 in anticipo sulla lettura di {1,number,\#} {1,choice,1\#frame|2\#frames}. + +#[VDP.java] +# +#int frameCount +FRAME_AHEAD_OF_READING=Fotogramma in anticipo sulla lettura di {0,number,\#} {0,choice,1\#frame|2\#frames}. + +#[VDP.java] +# +#String fileName,String frameNumber +FRAME_FILE_WRITE_UNABLE=Impossibile scrivere il file {0} per il fotogramma {1} + +#String formatIdentifier +VIDEO_FMT_IDENTIFIED=Formato video identificato come {0} + +#String frameNumber +FRAME_UNCOMPRESS_ERR=Errore decompressione del fotogramma {0} + +#[VDP.java] +# +#String frameNumber +JPEG_ENCODER_FRAME_FAIL=jPSXdec non pu\u00F2 gestire la codifica JPEG per il fotogramma {0}. Si prega di usare un altro formato. + +#[VDP.java] +JPEG_ENCODER_FRAME_FAIL_NO_FRAME=jPSXdec non pu\u00F2 gestire la codifica JPEG per il fotogramma. Si prega di usare un altro formato. + +#[VDP.java] +# +#int frameCount +WRITING_BLANK_FRAMES_TO_ALIGN_AV=Scrittura di {0,number,\#} {0,choice,1\#fotogramma vuoto|2\#fotogrammi vuoti} per poter sincronizzare audio/video. + +#[VDP.java] +# +#int frameCount +WRITING_DUP_FRAMES_TO_ALIGN_AV=Scrittura {0,number,\#} {0,choice,1\#fotogramma duplicato|2\#fotogrammi duplicati} per sincronizzare audio/video. + +#[VDP.java] +# +#java.io.File fileName,String frameNumber +FRAME_WRITE_ERR=Errore salvataggio del file {0} per il fotogramma {1} + +#[VDP.java] +# +#long sampleCount +WRITING_SILECE_TO_SYNC_AV=Scrittura di {0,number,\#} campioni audio muti per poter sincronizzare audio/video. + +#[VDP.java] +# +#long sampleCount +WRITING_SILENCE_TO_KEEP_AV_SYNCED=Aggiungo {0,number,\#} campioni audio vuoti per tenere la sincronizzazione. + +#[VideoFormat.java] +VID_IMG_SEQ_PNG_DESCRIPTION=Sequenza immagini\: PNG + +#[VideoFormat.java] +VID_IMG_SEQ_PNG_COMMAND=png + +#[VideoFormat.java] +VID_AVI_MJPG_DESCRIPTION=AVI\: Compressione MJPG + +#[VideoFormat.java] +VID_AVI_MJPG_COMMAND=avi\:mjpg + +#[VideoFormat.java] +VID_AVI_RGB_DESCRIPTION=AVI\: Non compresso in RGB + +#[VideoFormat.java] +VID_AVI_RGB_COMMAND=avi\:rgb + +#[VideoFormat.java] +VID_IMG_SEQ_BMP_DESCRIPTION=Sequenza immagini\: BMP + +#[VideoFormat.java] +VID_IMG_SEQ_BMP_COMMAND=bmp + +#[VideoFormat.java] +VID_IMG_SEQ_MDEC_DESCRIPTION=Sequenza immagini\: mdec + +#[VideoFormat.java] +VID_IMG_SEQ_MDEC_COMMAND=mdec + +#[VideoFormat.java] +VID_AVI_JYUV_DESCRIPTION=AVI\: YUV con raggio 0-255 + +#[VideoFormat.java] +VID_AVI_JYUV_COMMAND=avi\:jyuv + +#[VideoFormat.java] +VID_IMG_SEQ_JPG_DESCRIPTION=Sequenza immagini\: JPG + +#[VideoFormat.java] +VID_IMG_SEQ_JPG_COMMAND=jpg + +#[VideoFormat.java] +VID_IMG_SEQ_BS_DESCRIPTION=Sequenza immagini\: bitstream + +#[VideoFormat.java] +VID_IMG_SEQ_BS_COMMAND=bs + +#[VideoFormat.java] +VID_AVI_YUV_DESCRIPTION=AVI\: YUV con raggio 0-255 + +#[VideoFormat.java] +VID_AVI_YUV_COMMAND=avi\:yuv + +#[VideoSaverBuilder.java] +# +#java.io.File startFileName,java.io.File endFileName +VID_RANGE_OF_FILES_TO_SAVE={0}-{1} + +#[SpuSaverBuilder.java] +SPU_EXTENSION_DESCRIPTION=.spu (vag senza intestazione \= raw SPU data) + +#[SpuSaverBuilder.java] +VAG_EXTENSION_DESCRIPTION=.vag (formato 'Very Audio Good') + +#[VideoSaverBuilder.java] +# +#String qualityDescription +CMD_DECODE_QUALITY=Qualit\u00E0 decodifica\: {0} + +#[VideoSaverBuilder.java] +# +#java.io.File startFileName,java.io.File endFileName +CMD_OUTPUT_FILES=Output file\: {0}-{1} + +#[VideoSaverBuilder.java] +CMD_SAVING_WITH_AUDIO_ITEMS=Con file audio\: + +#[VideoSaverBuilder.java] +# +#int willEmulate +CMD_EMULATE_PSX_AV_SYNC_NY=Emula sincronizzazione PSX audio/videoEmulate PSX audio/video sync\: {0,choice,0\#No|1\#Si} + +#[VideoSaverBuilder.java] +CMD_NO_AUDIO=Senza sonoro + +#[VideoSaverBuilder.java, Command_Static.java] +# +#String upsampleDescription +CMD_UPSAMPLE_QUALITY=Sovracampionamento crominanza\: {0} + +#[VideoSaverBuilder.java] +# +#String videoFormatDescription +CMD_VIDEO_FORMAT=Formato video\: {0} + +#[VideoSaverBuilder.java] +# +#String startFrame +CMD_FRAME_RANGE_BEFORE=Salta fotogrammi prima di {0} + +#[VideoSaverBuilder.java] +# +#String endFrame +CMD_FRAME_RANGE_AFTER=Salta fotogrammi dopo {0} + +#[VideoSaverBuilder.java] +# +#int willCrop +CMD_CROPPING=Crop\: {0,choice,0\#No|1\#Si} + +#[VideoSaverBuilder.java] +# +#String videoFormatDescription +CMD_VIDEO_MUST_HAVE_EVEN_DIMS=La risoluzione del video {0} deve essere un numero pari, sistemo incrementando di un pixel + +#[VideoSaverBuilder.java] +# +#int width,int height +CMD_DIMENSIONS=Risoluzione\: {0,number,\#}x{1,number,\#} + +AVI_CLOSE_ERR=Errore chiusura AVI + +#[VideoSaverBuilder.java] +# +#java.io.File fileName +CMD_OUTPUT_FILE=File di destinazione\: {0} + +#[VideoSaverBuilder.java] +# +#int discSpeed,double framesPerSecond +CMD_DISC_SPEED=Velocit\u00E0 disco\: {0,choice,1\#1x|2\#2x} ({1,number,\#.\#\#\#} fps) + +#[VideoSaverBuilder.java] +CMD_VIDEO_DS=-ds + +#[VideoSaverBuilder.java] +CMD_VIDEO_DS_HELP=Specifica 1 o 2 se la velocit\u00E0 del disco \u00E8 ignota. + +#[VideoSaverBuilder.java] +CMD_VIDEO_UP=-up + +#[VideoSaverBuilder.java] +# +#ILocalizedMessage defaultUpsamplingMethod +CMD_VIDEO_UP_HELP=Metodo di sovracampionamento crominanza\n(predefinito {0}). Opzioni\: + +#String badQualityName +CMD_UPSAMPLE_QUALITY_INVALID=Qualit\u00E0 di sovracampionamento non valida {0} + +#String badQualityName +CMD_DECODE_QUALITY_INVALID=Valore di qualit\u00E0 di decodifica non valido {0} + +#[VideoSaverBuilder.java] +CMD_VIDEO_QUALITY=-quality,-q + +#[VideoSaverBuilder.java] +# +#ILocalizedMessage defaultQuality +CMD_VIDEO_QUALITY_HELP=Qualit\u00E0 di decodifica (predefinito {0}). Opzioni\: + +#[VideoSaverBuilder.java] +CMD_VIDEO_NOCROP=-nocrop + +#String badFrameNumberType +CMD_FRAME_NUMBER_TYPE_INVALID=Numero tipo fotoramma non valido {0} + +#[VideoSaverBuilder.java] +CMD_VIDEO_FRAMES=-start \#, -end \# + +#[VideoSaverBuilder.java] +CMD_VIDEO_FRAMES_HELP=Processa solo fotogrammi nel raggio. + +#[VideoSaverBuilder.java] +CMD_VIDEO_NUM=-num + +#[VideoSaverBuilder.java] +# +#ILocalizedMessage frameNumberType +CMD_VIDEO_NUM_HELP=Numero fotogrammi da usare durante il salvataggio\ndella sequenza immagini (predefinito {0}). Opzioni\: + +#String badFormatString +CMD_VIDEO_FORMAT_INVALID=Formato video non valido {0} + +#[VideoSaverBuilder.java] +CMD_VIDEO_VF=-vidfmt,-vf + +#[VideoSaverBuilder.java] +# +#ILocalizedMessage defaultVideoFormat +CMD_VIDEO_VF_HELP=Formato di destinazione (predefinito {0}).\nOpzioni\: + +#[VideoSaverBuilder.java] +CMD_VIDEO_HEADER_FRAME_NUMBER_UNSUPPORTED=Il video non supporta l'indicizzazione dei fotogrammi attraverso il numero di intestazione, -start -end -num verranno ignorati + +#String badFrameNumberString +CMD_FRAME_RANGE_INVALID=Fotogrammi non validi {0} + +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] +CMD_VIDEO_NOAUD=-noaud + +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] +CMD_VIDEO_NOAUD_HELP=Non salva l'audio + +#[PacketBasedVideoSaverBuilderGui.java] +GUI_SAVE_AUDIO_LABEL=Salva audio\: + +#[VideoSaverPanel.java] +GUI_DECODE_QUALITY_LABEL=Qualit\u00E0 decodifica\: + +#[VideoSaverPanel.java] +# +#long framesPerSecond +GUI_FPS_LABLE_WHOLE_NUMBER={0,number,\#} fps + +#[VideoSaverPanel.java] +# +#double decimalFramesPerSecond,long framesPerSecondNumerator,long framesPerSecondDenominator +GUI_FPS_LABEL_FRACTION={0,number,\#.\#\#\#} ({1,number,\#}/{2,number,\#}) fps + +#[VideoSaverPanel.java] +GUI_DIMENSIONS_LABEL=Risoluzione\: + +#[VideoSaverPanel.java] +# +#int width,int height +GUI_DIMENSIONS_WIDTH_X_HEIGHT_LABEL={0,number,\#}x{1,number,\#} + +#[VideoSaverPanel.java] +# +#java.io.File startFileName,java.io.File endFileName +GUI_OUTPUT_VIDEO_FILE_RANGE={0}\na\: {1} + +#[VideoSaverPanel.java] +GUI_DISC_SPEED_LABEL=Velocit\u00E0 disco\: + +#[VideoSaverPanel.java] +DISC_SPEED_1X=1x + +#[VideoSaverPanel.java] +DISC_SPEED_2X=2x + +GUI_AUDIO_VOLUME_LABEL=Volume audio\: + +#[VideoSaverPanel.java] +GUI_VIDEO_FORMAT_LABEL=Formato video\: + +#[VideoSaverPanel.java] +GUI_CROP_CHECKBOX=Ritaglio + +#[VideoSaverPanel.java] +GUI_CHROMA_UPSAMPLING_LABEL=Sovracampionamento crominanza\: + +#[SectorBasedVideoSaverBuilder.java] +CMD_VIDEO_PSXAV=-psxav + +#[SectorBasedVideoSaverBuilder.java] +CMD_VIDEO_PSXAV_HELP=Emula il timing audio/video della PSX. + +#[SectorBasedVideoSaverBuilderGui.java] +GUI_EMULATE_PSX_AV_SYNC_LABEL=Emula la sincronizzazione audio/video della PSX\: + +#[TimPaletteSelector.java] +GUI_COPY_TO_CLIPBOARD_TOOLTIP=Copia negli appunti + +#[TimSaverBuilder.java] +# +#java.io.File outputFile,int paletteIndex +CMD_PALETTE_IMAGE_SAVE_FAIL=Impossibile scrivere il file immagine {0} per la tavolozza colori {1,number,\#} + +#[TimSaverBuilder.java] +# +#String fileFormat +CMD_TIM_SAVE_FORMAT=Formato\: {0} + +#String badFileFormat +CMD_TIM_SAVE_FORMAT_INVALID=Formato non valido\: {0} + +#String badPaletteList +CMD_TIM_PALETTE_LIST_INVALID=Lista tavolozza colori non valida {0} + +#[TimSaverBuilder.java] +CMD_TIM_PAL=-pal <\#,\#-\#> + +#[TimSaverBuilder.java] +CMD_TIM_PAL_HELP=Tavolozza colori da salvare (tutti se non specificato) + +#[TimSaverBuilder.java] +CMD_TIM_IF=-imgfmt,-if + +#[TimSaverBuilder.java] +# +#String defaultImageFormat +CMD_TIM_IF_HELP=Formato destinazione immagine (predefinito {0}). Opzioni\: + +#ILocalizedMessage ouputFiles +CMD_TIM_PALETTE_FILES=File tavolozza colori\: {0} + +#[TimSaverBuilder.java] +# +#int fileCount,String startFileName,String endFileName +TIM_OUTPUT_FILES={0,number,\#} file tra {1}-{2} + +#[TimSaverBuilder.java] +TIM_OUTPUT_FILES_NONE=Nessuno + +#[TimSaverBuilder.java] +TIM_DATA_NOT_FOUND=Dati immagine TIM non trovati + +#[TimSaverBuilderGui.java] +GUI_TIM_SAVE_FORMAT_LABEL=Formato\: + +#[TimSaverBuilderGui.java] +# +#String listOfSourceCodeLineNumbers +GUI_TIM_ERR_READING_PREVIEW=Errore lettura anteprima TIM\n{0} + +#[DiscItemTim.java] +# +#int byteCount,int sectorNumber +CMD_TIM_REPLACE_SECTOR_BYTES=Scrittura {0,number,\#} di byte per il settore {1,number,\#} + +#[DiscItemTim.java] +# +#int timWidth,int timHeight,int paletteCount +GUI_TIM_IMAGE_DETAILS={0,number,\#}x{1,number,\#}, Tavolozza colori\: {2,number,\#} + +#[DiscItemTim.java] +# +#String newTimFormatDescription,String existingFormatDescription +TIM_INCOMPATIBLE=Nuovo formato TIM "{0}" non corrisponde a quello esistente "{1}" + +TIM_REPLACE_MULTI_CLUT_UNABLE=Impossibile rimpiazzare una TIM multi-palette con una semplice immagine + +#[*] +# +#String invalidValue,String command +CMD_INVALID_VALUE_FOR_CMD=Valore non valido "{0}" per {1} + +#[*] +# +#String invalidValue,String command +CMD_IGNORING_INVALID_VALUE_FOR_CMD=Ignoro valore non valido "{0}" per {1} + +#[*] +# +#String fileName +IO_OPENING_FILE=Apertura file {0} + +#[*] +IO_OPENING_FILE_NOT_FOUND=File non trovato + +#[*] +# +#String fileName +IO_OPENING_FILE_NOT_FOUND_NAME=File non trovato {0} + +#[*] +IO_OPENING_FILE_ERROR=Apertura file fallita + +#[*] +# +#String fileName +IO_OPENING_FILE_ERROR_NAME=Apertura file fallita {0} + +#[*] +IO_READING_FILE_ERROR=Errore lettura file + +#[*] +# +#String fileName +IO_READING_FILE_ERROR_NAME=Errore lettura file {0} + +#[*] +IO_READING_FROM_FILE_ERROR=Errore lettura dal file + +#[*] +# +#String fileName +IO_READING_FROM_FILE_ERROR_NAME=Errore lettura dal file {0} + +#[*] +# +#String fileName +IO_WRITING_FILE=Scrittura file {0} + +#[*] +IO_WRITING_FILE_ERROR=Errore scrittura file + +#[*] +# +#String fileName +IO_WRITING_FILE_ERROR_NAME=Errore scrittura file {0} + +#[*] +IO_WRITING_TO_FILE_ERROR=Errore scrittura sul file + +#[*] +# +#String fileName +IO_WRITING_TO_FILE_ERROR_NAME=Errore scrittura sul file {0} + +#[IO.java] +# +#java.io.File existingFileName +CANNOT_CREATE_DIR_OVER_FILE=Impossibile creare una cartella per il file {0} + +#[IO.java] +# +#java.io.File directoryName +UNABLE_TO_CREATE_DIR=Impossibile creare la cartella {0} + +#String directoryName +DIR_DOES_NOT_EXIST=La cartella {0} non esiste. diff --git a/jpsxdec/src/jpsxdec/i18n/Translations_ja.properties b/jpsxdec/src/jpsxdec/i18n/Translations_ja.properties index dc480d2..b01e632 100644 --- a/jpsxdec/src/jpsxdec/i18n/Translations_ja.properties +++ b/jpsxdec/src/jpsxdec/i18n/Translations_ja.properties @@ -1,5 +1,6 @@ # jPSXdec Translations -# Copyright (c) 2015-2017 Michael Sabin, Víctor González, Sergi Medina +# Copyright (c) 2015-2019 +# Michael Sabin, Víctor González, Sergi Medina, Gianluigi "Infrid" Cusimano # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,13 +48,9 @@ CMD_READING_INDEX_FILE=\u8AAD\u307F\u51FA\u3057\u30A4\u30F3\u30C7\u30C3\u30AF\u3 #java.io.File fileName CMD_INPUT_FILE_NOT_FOUND=\u5165\u529B\u30D5\u30A1\u30A4\u30EB{0}\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 -#[Command_Items.java] -# #String badItemNumber CMD_ITEM_NUMBER_INVALID=\u7121\u52B9\u9805\u76EE\u756A\u53F7\uFF1A{0} -#[Command_Items.java] -# #String badItemIdentifier CMD_ITEM_ID_INVALID=\u7121\u52B9\u306A\u30A2\u30A4\u30C6\u30E0\u8B58\u5225\u5B50\uFF1A{0} @@ -105,7 +102,7 @@ CMD_PROCESS_COMPLETE=\u30C7\u30A3\u30B9\u30AF\u306E\u30C7\u30B3\u30FC\u30C7\u30A #double durationInSeconds PROCESS_TIME=\u6642\u9593\uFF1A{0,number,\#.\#\#}\u79D2 -#[Command_Items.java] +#[Command_Items.java, Command_Static.java] # #int fileCount CMD_NUM_FILES_CREATED={0,number,\#}\u500B\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u4F5C\u6210 @@ -114,7 +111,7 @@ CMD_NUM_FILES_CREATED={0,number,\#}\u500B\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u4 CMD_REOPENING_DISC_WRITE_ACCESS=\u66F8\u304D\u8FBC\u307F\u30A2\u30AF\u30BB\u30B9\u3067\u30C7\u30A3\u30B9\u30AF\u30A4\u30E1\u30FC\u30B8\u3092\u518D\u30AA\u30FC\u30D7\u30F3\u3002 #[Command_Items.java] -CMD_BACKUP_DISC_IMAGE_WARNING=\u3053\u308C\u306F\u4E0D\u53EF\u9006\u7684\u3067\u3042\u308B\u306E\u3067\u3001\u3042\u306A\u305F\u306E\u30C7\u30A3\u30B9\u30AF\u30A4\u30E1\u30FC\u30B8\u3092\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3059\u308B\u9858\u3063\u3066\u3044\u307E\u3059\u3002 +CMD_BACKUP_DISC_IMAGE_WARNING=\u3053\u308C\u306F\u4E0D\u53EF\u9006\u7684\u3067\u3059\u306E\u3067\u3001\u3042\u306A\u305F\u306E\u30C7\u30A3\u30B9\u30AF\u30A4\u30E1\u30FC\u30B8\u3092\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3059\u308B\u9858\u3063\u3066\u3044\u307E\u3059\u3002 #[Command_Items.java] # @@ -131,7 +128,7 @@ CMD_PLAYER_ERR=\u30D7\u30EC\u30FC\u30E4\u30FC\u306E\u30A8\u30E9\u30FC CMD_XA_REPLACE_OPENING_PATCH_IDX=\u958B\u53E3\u30D1\u30C3\u30C1\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9{0} #[Command_Items.java, SavingGuiTask.java] -SAVE_LOG_FILE_BASE_NAME=\u30BB\u30FC\u30D6 +SAVE_LOG_FILE_BASE_NAME=\u4FDD\u5B58\u3059\u308B #[Command.java, CommandLine.java, Gui.java] INDEX_LOG_FILE_BASE_NAME=\u6307\u6570 @@ -156,8 +153,6 @@ USER_LOG_EXCEPTION=[{0}] {1} #String logLevel,String exceptionName,String exceptionMessage USER_LOG_EXCEPTION_MSG=[{0}] {1} {2} -#[Command_CopySect.java] -# #String badSectorRangeString CMD_SECTOR_RANGE_INVALID=\u7121\u52B9\u30BB\u30AF\u30BF\u306E\u7BC4\u56F2\uFF1A{0} @@ -172,8 +167,6 @@ CMD_GENERATING_SECTOR_LIST=\u30BB\u30AF\u30BF\u30EA\u30B9\u30C8\u3092\u4F5C\u621 #[Command_Static.java] CMD_DIM_OPTION_REQURIED=-dim\u30AA\u30D7\u30B7\u30E7\u30F3\u304C\u5FC5\u8981 -#[Command_Static.java] -# #String badQuality CMD_QUALITY_INVALID=\u7121\u52B9\u54C1\u8CEA{0} @@ -185,16 +178,12 @@ CMD_USING_QUALITY=\u8CEA\u3092\u4F7F\u7528\u3057\u3066{0} #[Command_Static.java] CMD_NOT_TIM=\u30A8\u30E9\u30FC\uFF1A\u306A\u3044\u30C6\u30A3\u30E0\u306E\u753B\u50CF -#[Command_Static.java] -# #ILocalizedMessage upsampleDescription CMD_USING_UPSAMPLING=\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3092\u4F7F\u7528\u3057\u3066{0} #[Command_Static.java] CMD_TIM_IO_ERR=\u8AAD\u307F\u8FBC\u307F\u30A8\u30E9\u30FC\u307E\u305F\u306FTIM\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u307E\u3059 -#[Command_Static.java] -# #String badFormat CMD_FORMAT_INVALID=\u7121\u52B9\u306A\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u30BF\u30A4\u30D7{0} @@ -209,8 +198,6 @@ CMD_ASSERT_DISABLED_NO_DEBUG_USE_EA=-ea\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u4F7 #java.io.File timFileName CMD_READING_TIM=\u8AAD\u307F\u53D6\u308ATIM\u30D5\u30A1\u30A4\u30EB{0} -#[Command_Static.java] -# #String badStaticTypeName CMD_STATIC_TYPE_INVALID=\u7121\u52B9\u306A\u9759\u7684\u578B\uFF1A{0} @@ -225,11 +212,6 @@ CMD_READING_STATIC_FILE=\u9759\u7684\u30D5\u30A1\u30A4\u30EB{0}\u3092\u8AAD\u307 #[Command_Static.java] CMD_IMAGE_CONVERT_OK=\u753B\u50CF\u306F\u6B63\u5E38\u306B\u5909\u63DB\u3057\u307E\u3057\u305F -#[Command_Static.java] -# -#String badUpsamplingName -CMD_UPSAMPLING_INVALID=\u7121\u52B9\u306A\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0{0} - #[Command_Static.java, VideoSaverBuilder.java] # #java.io.File fileName @@ -256,13 +238,9 @@ CMD_BUILDING_INDEX=\u30D3\u30EB\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 CMD_DISC_READ_ERROR=\u30C7\u30A3\u30B9\u30AF\u306F\u8AAD\u307F\u8FBC\u307F\u30A8\u30E9\u30FC\u3002 -#[CommandLine.java] -# #String badVerbosityLevel CMD_VERBOSE_LVL_INVALID_STR=\u7121\u52B9\u306A\u5197\u9577\u30EC\u30D9\u30EB{0} -#[CommandLine.java] -# #int badVerbosityNumber CMD_VERBOSE_LVL_INVALID_NUM=\u7121\u52B9\u306A\u5197\u9577\u30EC\u30D9\u30EB{0,number,\#} @@ -381,7 +359,7 @@ GUI_EXPAND_ALL_BTN=\u3059\u3079\u3066\u5C55\u958B GUI_SAVE_ALL_SELECTED_BTN=\u3059\u3079\u3066\u306E\u9078\u629E\u3092\u4FDD\u5B58 #[Gui.java] -GUI_NOTHING_IS_MARKED_FOR_SAVING=\u4F55\u3082\u4FDD\u5B58\u7528\u306B\u30DE\u30FC\u30AF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002 +GUI_NOTHING_IS_MARKED_FOR_SAVING=\u4F55\u3082\u4FDD\u5B58\u3059\u308B\u305F\u3081\u306B\u30DE\u30FC\u30AF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002 #[Gui.java] # @@ -403,7 +381,7 @@ GUI_SAVE_INDEX_PROMPT_TITLE=\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u4FDD\u5B GUI_PLAY_TAB=\u00A0\u00A0\u00A0\u00A0\u904A\u3073\u307E\u3059\u00A0\u00A0\u00A0\u00A0 #[Gui.java] -GUI_SAVE_TAB=\u00A0\u00A0\u00A0\u00A0\u30BB\u30FC\u30D6\u00A0\u00A0\u00A0\u00A0 +GUI_SAVE_TAB=\u00A0\u00A0\u00A0\u00A0\u4FDD\u5B58\u3059\u308B\u00A0\u00A0\u00A0\u00A0 #[Gui.java] GUI_PAUSE_BTN=\u4F11\u6B62 @@ -441,7 +419,7 @@ GUI_SECTORS_COLUMN=\u30BB\u30AF\u30BF\u30FC GUI_TREE_TYPE_COLUMN=\u30BF\u30A4\u30D7 #[GuiTree.java] -GUI_SELECT_NONE=\u306A\u3057 +GUI_SELECT_NONE=\u7121\u3057 #[GuiTree.java] GUI_SELECT_ALL_VIDEO=\u5168\u3066\u306E\u30D3\u30C7\u30AA @@ -461,13 +439,13 @@ GUI_SELECT_ALL_IMAGES=\u3059\u3079\u3066\u306E\u753B\u50CF #[GuiTree.java] GUI_SELECT_ALL_SOUNDS=\u3059\u3079\u3066\u306E\u30B5\u30A6\u30F3\u30C9\u30AF\u30EA\u30C3\u30D7 -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_DETAILS_COLUMN=\u7D30\u90E8 -#[VideoSaverBuilderStrGui.java, GuiTree.java] -GUI_TREE_SAVE_COLUMN=\u30BB\u30FC\u30D6 +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] +GUI_TREE_SAVE_COLUMN=\u4FDD\u5B58\u3059\u308B -#[VideoSaverBuilderStrGui.java, GuiTree.java] +#[SectorBasedVideoSaverBuilderGui.java, GuiTree.java] GUI_TREE_INDEX_NUMBER_COLUMN=\uFF03 #[IndexId.java] @@ -588,13 +566,9 @@ FAILED_TO_READ_1_SECTOR=\u5C11\u306A\u304F\u3068\u30821\u30BB\u30AF\u30BF\u5168\ #[SerializedDiscItem.java] EMPTY_SERIALIZED_STRING=\u7A7A\u306E\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u3055\u308C\u305F\u6587\u5B57\u5217 -#[SerializedDiscItem.java] -# #String badNumber SERIALIZATION_FAILED_TO_CONVERT_TO_INT=int\u306B\u30B7\u30EA\u30A2\u30EB\u5316\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u5909\u63DB\u3059\u308B\u305F\u3081\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{0} -#[SerializedDiscItem.java] -# #String badNumber SERIALIZATION_FAILED_TO_CONVERT_TO_LONG=long\u306B\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u30D5\u30A3\u30FC\u30EB\u30C9\u306E\u5909\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{0} @@ -621,7 +595,7 @@ SERIALIZATION_FIELD_NOT_FOUND={0}\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u898B\u306 #[DiscIndex.java] # #String actualFormatDescription,String expectedFormatDescription -CD_FORMAT_MISMATCH=\u30C7\u30A3\u30B9\u30AF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u306F\u3001 "{0}"\uFF01\= "{1}" \u8A00\u3046\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002 +CD_FORMAT_MISMATCH=\u30C7\u30A3\u30B9\u30AF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u300C{0}\u300D\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u30D5\u30A1\u30A4\u30EB\u306E\u5F62\u5F0F\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u300C{1}\u300D #[DiscIndex.java] INDEX_HEADER_MISSING=\u9069\u5207\u306A\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u30D8\u30C3\u30C0\u304C\u3042\u308A\u307E\u305B\u3093\u3002 @@ -633,13 +607,9 @@ INDEXING_ERROR=\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u305 #int sectorNumber INDEX_SECTOR_CORRUPTED=\u30BB\u30AF\u30BF{0,number,\#}\u306E\u7834\u640D\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002\u3053\u308C\u306F\u3001\u8B58\u5225\u304A\u3088\u3073\u5909\u63DB\u306B\u5F71\u97FF\u3092\u4E0E\u3048\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002 -#[DiscIndex.java] -# #int previousSectorNumber,int currentSectorNumber INDEX_SECTOR_HEADER_NUM_BREAK=\u975E\u9023\u7D9A\u7684\u306A\u30BB\u30AF\u30BF\u30D8\u30C3\u30C0\u756A\u53F7\uFF1A{0,number,\#} - > {1,number,\#} -#[DiscIndex.java] -# #int sectorNumber INDEX_MODE1_AMONG_MODE2=\u30BB\u30AF\u30BF\u306F\u3001{0,number,\#}\u304C\u30E2\u30FC\u30C91\u3001\u30E2\u30FC\u30C92\u306E\u30BB\u30AF\u30BF\u9593\u3067\u898B\u51FA\u3055\u308C\u307E\u3059 @@ -787,18 +757,16 @@ CMD_AUDIO_VOL=-vol <0-100> #int defaultVolumeLevel CMD_AUDIO_VOL_HELP=\u97F3\u91CF\u8ABF\u6574\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E{0,number,\#}\uFF09\u3002 -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidFormatName CMD_IGNORING_INVALID_FORMAT=\u7121\u52B9\u306A\u5F62\u5F0F\u3092\u7121\u8996{0} -#[AudioSaverBuilder.java, SpuSaverBuilder.java] +#[SpuSaverBuilder.java] # #String invalidVolume CMD_IGNORING_INVALID_VOLUME=\u7121\u52B9\u4F53\u7A4D\u3092\u7121\u8996{0} -#[VideoSaverBuilder.java] -# #String badDiscSpeed CMD_IGNORING_INVALID_DISC_SPEED=\u7121\u52B9\u306A\u30C7\u30A3\u30B9\u30AF\u901F\u5EA6\u3092\u7121\u8996{0} @@ -917,55 +885,55 @@ QUALITY_PSX_DESCRIPTION=PSX\uFF08\u4F4E\u3044\uFF09\u54C1\u8CEA\u3092\u30A8\u30D #[MdecDecodeQuality.java] QUALITY_PSX_COMMAND=PSX -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION=\u30D0\u30A4\u30AD\u30E5\u30FC\u30D3\u30C3\u30AF -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BICUBIC_CMDLINE=\u30D0\u30A4\u30AD\u30E5\u30FC\u30D3\u30C3\u30AF -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_DESCRIPTION=\u30D9\u30EB -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BELL_CMDLINE=\u30D9\u30EB -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION=\u6700\u8FD1\u508D -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE=NearestNeighbor -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION=Lanczos3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_LANCZOS3_CMDLINE=Lanczos3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION=\u30DF\u30C3\u30C1\u30A7\u30EB -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_MITCHELL_CMDLINE=\u30DF\u30C3\u30C1\u30A7\u30EB -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_DESCRIPTION=\u30A8\u30EB\u30DF\u30FC\u30C8 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_HERMITE_CMDLINE=\u30A8\u30EB\u30DF\u30FC\u30C8 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION=B\u30B9\u30D7\u30E9\u30A4\u30F3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BSPLINE_CMDLINE=B\u30B9\u30D7\u30E9\u30A4\u30F3 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION=\u30D0\u30A4\u30EA\u30CB\u30A2 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] CHROMA_UPSAMPLE_BILINEAR_CMDLINE=\u30D0\u30A4\u30EA\u30CB\u30A2 -#[MdecDecoder_double_interpolate.java] +#[ChromaUpsample.java] # #ILocalizedMessage commandLineId,ILocalizedMessage interplationName CHROMA_UPSAMPLE_CMDLINE_HELP={0}\uFF08{1}\uFF09 @@ -1010,10 +978,12 @@ CMD_UNABLE_TO_IDENTIFY_FRAME_TYPE=\u30D5\u30EC\u30FC\u30E0\u30BF\u30A4\u30D7\u30 #[ReplaceFrameFull.java, ReplaceFramePartial.java] # #String frameNumber,int maxSize -CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH=\u30D5\u30EC\u30FC\u30E0\u3092\u5727\u7E2E\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093{0} {1,number,\#}\u30D0\u30A4\u30C8\u306B\u53CE\u307E\u308B\u307B\u3069\u306E\u5C0F\u3055\u306A\!\!\! +CMD_UNABLE_TO_COMPRESS_FRAME_SMALL_ENOUGH={1,number,\#}\u30D0\u30A4\u30C8\u306B\u53CE\u307E\u308B\u3088\u3046\u306B\u5341\u5206{0}\u5C0F\u3055\u306A\u30D5\u30EC\u30FC\u30E0\u3092\u5727\u7E2E\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093 #[ReplaceFramePartial.java] -CMD_NO_DIFFERENCE_SKIPPING=\u5DEE\u7570\u306F\u30B9\u30AD\u30C3\u30D7\u3057\u3001\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002 +# +#String frameNumber +CMD_NO_DIFFERENCE_SKIPPING=\u30D5\u30EC\u30FC\u30E0\u306B\u898B\u3089\u308C\u308B\u5DEE\u7570{0}\u3001\u30B9\u30AD\u30C3\u30D7\u3002 #[ReplaceFramePartial.java] # @@ -1029,7 +999,9 @@ CMD_ENTIRE_FRAME_DIFFERENT=\u8B66\u544A\uFF1A\u5168\u4F53\u306E\u30D5\u30EC\u30F REPLACE_UNABLE_READ_IMAGE=\u753B\u50CF\u3068\u3057\u3066{0}\u3092\u30ED\u30FC\u30C9\u3067\u304D\u307E\u305B\u3093 #[ReplaceFramePartial.java] -REPLACE_FRAME_DIMENSIONS_TOO_SMALL=\u30BD\u30FC\u30B9\u30D5\u30EC\u30FC\u30E0\u3088\u308A\u5C0F\u3055\u3044\u4EA4\u63DB\u30D5\u30EC\u30FC\u30E0\u5BF8\u6CD5 +# +#int newWidth,int newHeight,int existingWidth,int existingHeight +REPLACE_FRAME_DIMENSIONS_TOO_SMALL=\u4EA4\u63DB\u30D5\u30EC\u30FC\u30E0\u5BF8\u6CD5{0,number,\#} X {1,number,\#}\u30BD\u30FC\u30B9\u30D5\u30EC\u30FC\u30E0{2,number,\#} X {3,number,\#}\u3088\u308A\u3082\u5C0F\u3055\u3044\u3067\u3059 #[ReplaceFrames.java] # @@ -1062,16 +1034,12 @@ REPLACE_FRAME_TYPE_NOT_IKI=\u30D5\u30EC\u30FC\u30E0\u30BF\u30A4\u30D7\u306F\u58F FRAME_NOT_IKI=\u30D5\u30EC\u30FC\u30E0\u306F\u3001\u58F1\u5C90\u306E\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093 -#[BitStreamUncompressor_Lain.java] FRAME_NOT_LAIN=\u30D5\u30EC\u30FC\u30E0\u306F\u3001\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u3092\u30EC\u30A4\u30F3\u308C\u3066\u3044\u307E\u305B\u3093 -#[BitStreamUncompressor_STRv1.java] FRAME_NOT_STRV1=\u30D5\u30EC\u30FC\u30E0\u306FSTRv1\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093 -#[BitStreamUncompressor_STRv2.java] FRAME_NOT_STRV2=\u30D5\u30EC\u30FC\u30E0\u306FSTRv2\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093 -#[BitStreamUncompressor_STRv3.java] FRAME_NOT_STRV3=\u30D5\u30EC\u30FC\u30E0\u306FSTRv3\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093 #[SectorIkiVideo.java] @@ -1087,17 +1055,15 @@ UNEXPECTED_END_OF_AUDIO=\u30AA\u30FC\u30C7\u30A3\u30AA\u30C7\u30FC\u30BF\u306E\u #[BitStreamUncompressor_Iki.java] # #int macroBlockX,int macroBlockY,int quantizationScale -IKI_REDUCING_QSCALE_OF_MB_TO_VAL={2,number,\#}\u3068\u306EQscale\uFF08{0,number,\#}\u3001{1,number,\#}\uFF09\u3092\u4F4E\u6E1B\u3057\u3088\u3046\u3068\u3057 +IKI_REDUCING_QSCALE_OF_MB_TO_VAL={2,number,\#}\u306B\uFF08{1,number,\#}\u3001{0,number,\#}\uFF09\u30DE\u30AF\u30ED\u30D6\u30ED\u30C3\u30AF\u306E\u91CF\u5B50\u5316\u30B9\u30B1\u30FC\u30EB\u3092\u4F4E\u6E1B\u3057\u3088\u3046\u3068 -#[BitStreamUncompressor_Iki.java] -# #String frameNumber,int demuxSize,int sourceSize IKI_NEW_FRAME_GT_SRC_STOPPING=\u65B0\u3057\u3044\u30D5\u30EC\u30FC\u30E0{0} DEMUX\u30B5\u30A4\u30BA{1,number,\#}> max\u306E\u30BD\u30FC\u30B9{2,number,\#}\u3001\u305D\u3046\u505C\u6B62 #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java] # #String frameNumber,int demuxSize,int sourceSize -NEW_FRAME_FITS=\u65B0\u3057\u3044\u30D5\u30EC\u30FC\u30E0{0} DEMUX\u30B5\u30A4\u30BA{1,number,\#} <\= max\u306E\u30BD\u30FC\u30B9{2,number,\#} +NEW_FRAME_FITS=\u65B0\u3057\u3044\u30D5\u30EC\u30FC\u30E0{0}\u4EA4\u63DB\u30B5\u30A4\u30BA{1,number,\#}\u65E2\u5B58\u306E\u5229\u7528\u53EF\u80FD\u306A\u30B5\u30A4\u30BA\u5185\u306B\u53CE\u307E\u308B{2,number,\#} #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_STRv2.java] # @@ -1109,25 +1075,25 @@ END_OF_STREAM=\u30B9\u30C8\u30EA\u30FC\u30E0\u306E\u7D42\u308F\u308A #[BitStreamUncompressor_Lain.java] # #int lumaQuantizationScale,int chromaQuantizationScale -TRYING_LUMA_CHROMA=\u30EB\u30DE{0,number,\#}\u30AF\u30ED\u30DE{1,number,\#}\u3057\u3088\u3046 +TRYING_LUMA_CHROMA=\u30EB\u30DE\u91CF\u5B50\u5316\u30B9\u30B1\u30FC\u30EB{0,number,\#}\u304A\u3088\u3073\u30AF\u30ED\u30DE\u91CF\u5B50\u5316\u30B9\u30B1\u30FC\u30EB{1,number,\#}\u3067\u5727\u7E2E\u3057\u3088\u3046\u3068\u3057\u307E\u3059 #[BitStreamUncompressor_Iki.java, BitStreamUncompressor_Lain.java, BitStreamUncompressor_STRv2.java, ReplaceFrameFull.java] # #String frameNumber,int newFrameSize,int sourceFrameSize -NEW_FRAME_DOES_NOT_FIT=\!\!\!\u65B0\u3057\u3044\u30D5\u30EC\u30FC\u30E0{0} DEMUX\u30B5\u30A4\u30BA{1,number,\#}> max\u306E\u30BD\u30FC\u30B9{2,number,\#}\uFF01 +NEW_FRAME_DOES_NOT_FIT=\u65B0\u3057\u3044\u30D5\u30EC\u30FC\u30E0{0}\u4EE3\u66FF\u30B5\u30A4\u30BA{1,number,\#}\u65E2\u5B58\u306E\u5229\u7528\u53EF\u80FD\u30B5\u30A4\u30BA\u306B\u53CE\u307E\u3089\u306A\u3044{2,number,\#} #[BitStreamUncompressor_Lain.java] # #String frameNumber -COMPRESS_TOO_MUCH_ENERGY=\u30D3\u30C7\u30AA\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u306F\u3001\u30D5\u30EC\u30FC\u30E0\u306E\u305F\u3081\u306E\u65B0\u305F\u306A\u30C7\u30FC\u30BF\u306E\u5927\u304D\u3055\u3092\u6271\u3046\u3053\u3068\u304C\u3067\u304D\u306A\u3044{0} +COMPRESS_TOO_MUCH_ENERGY=\u30D5\u30EC\u30FC\u30E0{0}\u306E\u7F6E\u63DB\u753B\u50CF\u3092\u5727\u7E2E\u3059\u308B\u306B\u306F\u3042\u307E\u308A\u306B\u3082\u8A73\u7D30\u3067\u3059 #int currentWidth,int newWidth INCONSISTENT_WIDTH=\u77DB\u76FE\u5E45{0,number,\#}\uFF01\= {1,number,\#} -#[VideoSaverBuilderCrusader.java] +#[PacketBasedVideoSaverBuilder.java] # #int audioSampleRate -EMBEDDED_CRUSADER_AUDIO_HZ=\u57CB\u3081\u8FBC\u307F\u5341\u5B57\u8ECD\u30AA\u30FC\u30C7\u30A3\u30AA{0,number,\#}\u30D8\u30EB\u30C4 +CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ=\u57CB\u3081\u8FBC\u307E\u308C\u305F\u30AA\u30FC\u30C7\u30A3\u30AA{0,number,\#}\u30D8\u30EB\u30C4 CRUSADER_VIDEO_CORRUPTED=\u30AF\u30EB\u30BB\u30A4\u30C0\u30FC\uFF1A\u3044\u3044\u3048\u5F8C\u6094\u30D3\u30C7\u30AA\u304C\u7834\u640D\u3057\u3066\u3044\u307E\u305B\u3093 @@ -1136,25 +1102,21 @@ CRUSADER_AUDIO_CORRUPTED=\u30AF\u30EB\u30BB\u30A4\u30C0\u30FC\uFF1A\u3044\u3044\ #String frameNumber,int chunkNumber MISSING_CHUNK=\u30D5\u30EC\u30FC\u30E0{0}\u30C1\u30E3\u30F3\u30AF{1,number,\#}\u6B20\u843D\u3002 -#[SectorFrameBuilder.java] -# #int sectorNumber,int currentChunkCount,int newChunkCount DEMUX_FRAME_CHUNKS_CHANGED_FROM_TO=\u30D5\u30EC\u30FC\u30E0\u5185\u306E\u30BB\u30AF\u30BF{0,number,\#}\u30C1\u30E3\u30F3\u30AF\u306F\u3001{2,number,\#}\u306B{1,number,\#}\u306B\u5909\u66F4\u3057\u307E\u3057\u305F -#[SectorFrameBuilder.java] -# #int sectorNumber,int chunkNumber,int chunksInFrame DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME=\u30BB\u30AF\u30BF\u30FC\u30D5\u30EC\u30FC\u30E0\u306B\u304A\u3051\u308B{0,number,\#}\u30C1\u30E3\u30F3\u30AF\u756A\u53F7{1,number,\#}> \=\u30C1\u30E3\u30F3\u30AF{2,number,\#} -#[SectorFrameBuilder.java] +#[SectorBasedFrameBuilder.java] # #int frameStartSector,int frameEndSector,int chunkNumber MISSING_CHUNK_FRAME_IN_SECTORS=\u30BB\u30AF\u30BF\u5185\u306E\u30D5\u30EC\u30FC\u30E0{0,number,\#} - {1,number,\#}\u6B20\u843D\u3057\u3066\u3044\u308B\u30C1\u30E3\u30F3\u30AF{2,number,\#} #[DemuxedCrusaderFrame.java, SectorBasedFrameReplace.java] -CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=\u30C1\u30E3\u30F3\u30AF\u304C\u6B20\u843D\u3057\u3066\u30D5\u30EC\u30FC\u30E0\u3092\u4EA4\u63DB\u3057\u3088\u3046\u3068\u3059\u308B\u3068\uFF1F +CMD_FRAME_TO_REPLACE_MISSING_CHUNKS=\u65E2\u5B58\u306E\u7834\u640D\u3057\u305F\u30D5\u30EC\u30FC\u30E0\u3092\u4EA4\u63DB\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307E\u3059 -#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java] +#[VDP.java, ReplaceFrameFull.java, ReplaceFramePartial.java, SectorBasedFrameBuilder.java] # #String frameNumber FRAME_NUM_CORRUPTED=\u30D5\u30EC\u30FC\u30E0{0}\u306E\u30A8\u30E9\u30FC\uFF1A\u30D5\u30EC\u30FC\u30E0\u304C\u7834\u640D\u3057\u3066\u3044\u307E\u3059 @@ -1205,7 +1167,7 @@ JPEG_ENCODER_FRAME_FAIL_NO_FRAME=\u30B7\u30F3\u30D7\u30EBjPSXdec JPEG\u30A8\u30F #[VDP.java] # #int frameCount -WRITING_BLANK_FRAMES_TO_ALIGN_AV=\u30AA\u30FC\u30C7\u30A3\u30AA/\u30D3\u30C7\u30AA\u518D\u751F\u3092\u6574\u5217\u3055\u305B\u308B\u305F\u3081\u306B\u3001{0,number,\#}\u30D6\u30E9\u30F3\u30AF\u30D5\u30EC\u30FC\u30E0\uFF08\u8907\u6570\u53EF\uFF09\u3092\u66F8\u304D\u8FBC\u307F\u307E\u3059\u3002 +WRITING_BLANK_FRAMES_TO_ALIGN_AV=\u30AA\u30FC\u30C7\u30A3\u30AA/\u30D3\u30C7\u30AA\u518D\u751F\u3092\u6574\u5217\u3055\u305B\u308B\u305F\u3081\u306B\u3001{0,number,\#}\u30D6\u30E9\u30F3\u30AF\u30D5\u30EC\u30FC\u30E0\u3092\u66F8\u304D\u8FBC\u307F\u307E\u3059\u3002 #[VDP.java] # @@ -1303,7 +1265,7 @@ CMD_SAVING_WITH_AUDIO_ITEMS=\u30AA\u30FC\u30C7\u30A3\u30AA\u9805\u76EE\u3092\uFF #[VideoSaverBuilder.java] CMD_NO_AUDIO=\u30AA\u30FC\u30C7\u30A3\u30AA\u304C\u805E\u3053\u3048\u307E\u305B\u3093\u3002 -#[VideoSaverBuilder.java] +#[VideoSaverBuilder.java, Command_Static.java] # #String upsampleDescription CMD_UPSAMPLE_QUALITY=\u30AF\u30ED\u30DE\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\uFF1A{0} @@ -1344,13 +1306,9 @@ CMD_VIDEO_UP=-up <\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0> #ILocalizedMessage defaultUpsamplingMethod CMD_VIDEO_UP_HELP=\u30AF\u30ED\u30DE\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u65B9\u6CD5\n\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8{0}\uFF09\u3002\u30AA\u30D7\u30B7\u30E7\u30F3\uFF1A -#[VideoSaverBuilder.java] -# #String badQualityName CMD_UPSAMPLE_QUALITY_INVALID=\u7121\u52B9\u306A\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EB\u54C1\u8CEA{0} -#[VideoSaverBuilder.java] -# #String badQualityName CMD_DECODE_QUALITY_INVALID=\u7121\u52B9\u30C7\u30B3\u30FC\u30C9\u54C1\u8CEA{0} @@ -1368,13 +1326,11 @@ CMD_VIDEO_NOCROP=-nocrop #[VideoSaverBuilder.java] CMD_VIDEO_NOCROP_HELP=\u672A\u4F7F\u7528\u306E\u30D5\u30EC\u30FC\u30E0\u306E\u30A8\u30C3\u30B8\u306E\u5468\u308A\u306E\u30C7\u30FC\u30BF\u3092\u30AF\u30ED\u30C3\u30D7\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002 -#[VideoSaverBuilder.java] -# #String badFrameNumberType CMD_FRAME_NUMBER_TYPE_INVALID=\u7121\u52B9\u306A\u30D5\u30EC\u30FC\u30E0\u756A\u53F7\u306E\u30BF\u30A4\u30D7{0} #[VideoSaverBuilder.java] -CMD_VIDEO_FRAMES=-frame\u306F\u3001\uFF03\u307E\u305F\u306F\uFF03\u3092-frames - \uFF03 +CMD_VIDEO_FRAMES=-start \#, -end \# #[VideoSaverBuilder.java] CMD_VIDEO_FRAMES_HELP=\u30D7\u30ED\u30BB\u30B9\u306F\u3001\u7BC4\u56F2\u5185\u306E\u30D5\u30EC\u30FC\u30E0\u3002 @@ -1387,8 +1343,6 @@ CMD_VIDEO_NUM=-num <\u30BF\u30A4\u30D7> #ILocalizedMessage frameNumberType CMD_VIDEO_NUM_HELP=\u30A4\u30E1\u30FC\u30B8\u30B7\u30FC\u30B1\u30F3\u30B9\u3092\u4FDD\u5B58\u3059\u308B\u3068\u304D\u306B\u30D5\u30EC\u30FC\u30E0\u756A\u53F7\u3092\u4F7F\u7528\u3059\u308B\u306B\u306F\n\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8{0}\uFF09\u3002\u30AA\u30D7\u30B7\u30E7\u30F3\uFF1A -#[VideoSaverBuilder.java] -# #String badFormatString CMD_VIDEO_FORMAT_INVALID=\u7121\u52B9\u306A\u30D3\u30C7\u30AA\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8{0} @@ -1406,13 +1360,13 @@ CMD_VIDEO_HEADER_FRAME_NUMBER_UNSUPPORTED=\u30D3\u30C7\u30AA\u306F\u3001\u30D8\u #String badFrameNumberString CMD_FRAME_RANGE_INVALID=\u7121\u52B9\u30D5\u30EC\u30FC\u30E0\uFF08S\uFF09{0} -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD=-noaud -#[VideoSaverBuilderCrusader.java, VideoSaverBuilderStr.java] +#[PacketBasedVideoSaverBuilder.java, SectorBasedVideoSaverBuilder.java] CMD_VIDEO_NOAUD_HELP=\u30AA\u30FC\u30C7\u30A3\u30AA\u3092\u4FDD\u5B58\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002 -#[VideoSaverBuilderCrusaderGui.java] +#[PacketBasedVideoSaverBuilderGui.java] GUI_SAVE_AUDIO_LABEL=\u30AA\u30FC\u30C7\u30A3\u30AA\u3092\u4FDD\u5B58\u3057\u307E\u3059\u3002 #[VideoSaverPanel.java] @@ -1461,13 +1415,13 @@ GUI_CROP_CHECKBOX=\u53CE\u7A6B #[VideoSaverPanel.java] GUI_CHROMA_UPSAMPLING_LABEL=\u30AF\u30ED\u30DE\u30A2\u30C3\u30D7\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\uFF1A -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV=-psxav -#[VideoSaverBuilderStr.java] +#[SectorBasedVideoSaverBuilder.java] CMD_VIDEO_PSXAV_HELP=PSX\u30AA\u30FC\u30C7\u30A3\u30AA/\u30D3\u30C7\u30AA\u30FB\u30BF\u30A4\u30DF\u30F3\u30B0\u3092\u30A8\u30DF\u30E5\u30EC\u30FC\u30C8\u3057\u307E\u3059\u3002 -#[VideoSaverBuilderStrGui.java] +#[SectorBasedVideoSaverBuilderGui.java] GUI_EMULATE_PSX_AV_SYNC_LABEL=PSX\u306EA / V\u540C\u671F\u3092\u30A8\u30DF\u30E5\u30EC\u30FC\u30C8\uFF1A #[TimPaletteSelector.java] @@ -1483,13 +1437,9 @@ CMD_PALETTE_IMAGE_SAVE_FAIL=\u30D1\u30EC\u30C3\u30C8{1,number,\#}\u306E\u753B\u5 #String fileFormat CMD_TIM_SAVE_FORMAT=\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\uFF1A{0} -#[TimSaverBuilder.java] -# #String badFileFormat CMD_TIM_SAVE_FORMAT_INVALID=\u7121\u52B9\u306A\u5F62\u5F0F{0} -#[TimSaverBuilder.java] -# #String badPaletteList CMD_TIM_PALETTE_LIST_INVALID=\u30D1\u30EC\u30C3\u30C8\u306E\u7121\u52B9\u30EA\u30B9\u30C8{0} @@ -1516,7 +1466,7 @@ CMD_TIM_PALETTE_FILES=\u30D1\u30EC\u30C3\u30C8\u30D5\u30A1\u30A4\u30EB\uFF1A{0} TIM_OUTPUT_FILES=\u9593{0,number,\#}\u500B\u306E\u30D5\u30A1\u30A4\u30EB{1} - {2} #[TimSaverBuilder.java] -TIM_OUTPUT_FILES_NONE=\u306A\u3057 +TIM_OUTPUT_FILES_NONE=\u7121\u3057 #[TimSaverBuilder.java] TIM_DATA_NOT_FOUND=TIM\u306E\u753B\u50CF\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 diff --git a/jpsxdec/src/jpsxdec/i18n/main_cmdline_help.dat b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help.dat index 0efcb4f..006f6c0 100644 --- a/jpsxdec/src/jpsxdec/i18n/main_cmdline_help.dat +++ b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help.dat @@ -28,9 +28,6 @@ java -jar jpsxdec.jar [ -x ] [ -f ] -help/-h/-? Display help about the index item - -play - Show real-time player for index item (audio/video items only) - (see manual or item's help for full list of possible commands) -visualize @@ -65,9 +62,4 @@ java -jar jpsxdec.jar -f -debug Show detailed decoding steps (needs Java started with -ea) -Universal option (optional): - -verbose/-v # - How much info to print: - 0 = none, 1 = only errors, 2 = errors & warnings, 3 = normal, 4 = extra - For all command-line options, see the manual. \ No newline at end of file diff --git a/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_es.dat b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_es.dat index 2244259..83252ba 100644 --- a/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_es.dat +++ b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_es.dat @@ -32,10 +32,6 @@ java -jar jpsxdec.jar [ -x ] [ -f ] -help/-h/-? Muestra la ayuda del objeto del indice. - -play - Muestra el reproductor para el objeto del indice - (solo videos y/o sonidos) - (ver el manual o la ayuda del objeto para revisar la lista completa de comandos disponibles) @@ -73,10 +69,4 @@ java -jar jpsxdec.jar -f Muestra los pasos detallados de decodificaciĂłn (necesita que Java estĂ© iniciado con -ea). -Opcion universal (opcional): - -verbose/-v # - Cuanta informacion se debe escribir: - 0 = nada, 1 = solo errores, 2 = errores y advertencias, - 3 = normal, 4 = extra - Revisa el manual para conocer todos los comandos disponibles. \ No newline at end of file diff --git a/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_it.dat b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_it.dat new file mode 100644 index 0000000..8c93f3e --- /dev/null +++ b/jpsxdec/src/jpsxdec/i18n/main_cmdline_help_it.dat @@ -0,0 +1,69 @@ + +java -jar jpsxdec.jar [ or ] + Mostra l'interfaccia grafica, anche durante l'apertura di + o (rilevamento automatico) + +java -jar jpsxdec.jar <-?, -h, -help> + Mostra questo aiuto + +java -jar jpsxdec.jar -f -x + Crea un indice di per salvarlo come + +java -jar jpsxdec.jar [ -x ] [ -f ] + + Comando principale dove è richiesto un file indice. + Usa un esistente (ignorando anche il indicato + al suo interno) oppure crea un indice al volo per (salvandolo + eventualmente come ) ed eseguendo uno dei seguenti comando + + + -item/-i <#, id> + -all/-a + Esegue su una voce dell'indice, + oppure su tutte le voci di un unico tipo (audio, video, file, immagine) + + Se nessun altro comando è specificato: + Estrate la voce dall'indice processandola con i parametri facoltativi + (usa la funzione di aiuto sulla voce per conoscere i parametri) + + -help/-h/-? + Mostra informazioni di aiuto riguardo la voce dell'indice + + (consulta il manuale o la funzione di aiuto sulla voce interessata, + per una lista di possibili comandi) + + -visualize + Mostra la disposizione dei settori e delle voci dell'indice + +java -jar jpsxdec.jar -f + Comando principale dove è richiesto solo un file in ingresso. + + -copysect <#, #-#> + Copia settori su un altro file + + -sectordump + Genera una lista in dei tipi di settori rilevati + (utile per fare debug) + + -static + Per bs o mdec (nessun parametro aggiuntivo richiesto per file TIM): + + -dim x + Risoluzione del fotogramma (parametro obbligatorio) + + -quality/-q + QualitĂ  di decodifica (valore predefinito "alto"). + + -fmt + Formato di destinazione (tipo predefinito PNG). + + -up + Medoto per sovracampionare la crominanza (metodo predefinito Bicubic) + Opzioni: NearestNeighbor, Bilinear, Bicubic, Bell, + Mitchell, BSpline, Lanczos3, Hermite + + -debug + Mostra informazioni dettagliate sul processo di decodifica + (Java deve essere avviato col parametro -ea) + +Per ulteriori dettagli sui comandi, consulta il manuale. \ No newline at end of file diff --git a/jpsxdec/src/jpsxdec/indexing/DiscIndex.java b/jpsxdec/src/jpsxdec/indexing/DiscIndex.java index 99d248e..cdf9ef4 100644 --- a/jpsxdec/src/jpsxdec/indexing/DiscIndex.java +++ b/jpsxdec/src/jpsxdec/indexing/DiscIndex.java @@ -73,7 +73,6 @@ import jpsxdec.modules.sharedaudio.DiscItemAudioStream; import jpsxdec.modules.strvideo.DiscItemStrVideoStream; import jpsxdec.util.IO; -import jpsxdec.util.IOException6; import jpsxdec.util.Misc; import jpsxdec.util.TaskCanceledException; @@ -101,7 +100,7 @@ public IndexNotFoundException(@Nonnull File file, FileNotFoundException ex) { } } - public static class IndexReadException extends IOException6 { + public static class IndexReadException extends IOException { @Nonnull private final File _file; @@ -599,8 +598,10 @@ public void indexingSectorRead(@Nonnull CdSector cdSector) { int iNewSectNumber = h.calculateSectorNumber(); if (iNewSectNumber != -1) { if (_iCurrentHeaderSectorNumber >= 0) { - if (_iCurrentHeaderSectorNumber + 1 != iNewSectNumber) - _log.log(Level.WARNING, I.INDEX_SECTOR_HEADER_NUM_BREAK(_iCurrentHeaderSectorNumber, iNewSectNumber)); + if (_iCurrentHeaderSectorNumber + 1 != iNewSectNumber) { + _log.log(Level.WARNING, I.INDEX_SECTOR_CORRUPTED_AT(cdSector.getSectorIndexFromStart())); + LOG.log(Level.WARNING, "Non-continuous sector header number: {0} -> {1}", new Object[]{_iCurrentHeaderSectorNumber, iNewSectNumber}); + } } _iCurrentHeaderSectorNumber = iNewSectNumber; } else { @@ -612,8 +613,10 @@ public void indexingSectorRead(@Nonnull CdSector cdSector) { switch (cdSector.getType()) { case MODE1: - if (_iMode1Count < _iMode2Count) - _log.log(Level.WARNING, I.INDEX_MODE1_AMONG_MODE2(cdSector.getSectorIndexFromStart())); + if (_iMode1Count < _iMode2Count) { + _log.log(Level.WARNING, I.INDEX_SECTOR_CORRUPTED_AT(cdSector.getSectorIndexFromStart())); + LOG.log(Level.WARNING, "Sector {0} is Mode 1 found among Mode 2 sectors", new Object[]{cdSector.getSectorIndexFromStart()}); + } _iMode1Count++; break; case UNKNOWN2048: diff --git a/jpsxdec/src/jpsxdec/indexing/DiscIndexer.java b/jpsxdec/src/jpsxdec/indexing/DiscIndexer.java index 1901ef5..e293590 100644 --- a/jpsxdec/src/jpsxdec/indexing/DiscIndexer.java +++ b/jpsxdec/src/jpsxdec/indexing/DiscIndexer.java @@ -55,8 +55,10 @@ import jpsxdec.modules.crusader.DiscIndexerCrusader; import jpsxdec.modules.dredd.DiscIndexerDredd; import jpsxdec.modules.iso9660.DiscIndexerISO9660; +import jpsxdec.modules.policenauts.DiscIndexerPolicenauts; +import jpsxdec.modules.roadrash.DiscIndexerRoadRash; import jpsxdec.modules.spu.DiscIndexerSpu; -import jpsxdec.modules.square.DiscIndexerSquare; +import jpsxdec.modules.square.DiscIndexerSquareAudio; import jpsxdec.modules.strvideo.DiscIndexerStrVideo; import jpsxdec.modules.tim.DiscIndexerTim; import jpsxdec.modules.xa.DiscIndexerXaAudio; @@ -71,13 +73,15 @@ public abstract class DiscIndexer { public static @Nonnull List createIndexers(@Nonnull ILocalizedLogger log) { DiscIndexer[] coreIndexers = new DiscIndexer[] { new DiscIndexerISO9660(log), - new DiscIndexerSquare(log), + new DiscIndexerSquareAudio(log), new DiscIndexerTim(), new DiscIndexerStrVideo(log), new DiscIndexerAceCombat3Video(log), new DiscIndexerXaAudio(log), + new DiscIndexerPolicenauts(), new DiscIndexerCrusader(log), new DiscIndexerDredd(log), + new DiscIndexerRoadRash(), }; ArrayList indexers = new ArrayList(Arrays.asList(coreIndexers)); diff --git a/jpsxdec/src/jpsxdec/modules/IdentifiedSector.java b/jpsxdec/src/jpsxdec/modules/IdentifiedSector.java index 368f5c9..354179a 100644 --- a/jpsxdec/src/jpsxdec/modules/IdentifiedSector.java +++ b/jpsxdec/src/jpsxdec/modules/IdentifiedSector.java @@ -86,6 +86,7 @@ final public int getProbability() { } /** Returns a string description of the sector type. */ + @Override public String toString() { return _sourceCdSector.toString(); } diff --git a/jpsxdec/src/jpsxdec/modules/SectorClaimSystem.java b/jpsxdec/src/jpsxdec/modules/SectorClaimSystem.java index 8e66331..8010b57 100644 --- a/jpsxdec/src/jpsxdec/modules/SectorClaimSystem.java +++ b/jpsxdec/src/jpsxdec/modules/SectorClaimSystem.java @@ -50,6 +50,8 @@ import jpsxdec.modules.crusader.SectorClaimToSectorCrusader; import jpsxdec.modules.dredd.SectorClaimToDreddFrame; import jpsxdec.modules.iso9660.SectorClaimToSectorISO9660; +import jpsxdec.modules.policenauts.SectorClaimToPolicenauts; +import jpsxdec.modules.roadrash.SectorClaimToRoadRash; import jpsxdec.modules.square.SectorClaimToSquareAudioSector; import jpsxdec.modules.strvideo.SectorClaimToStrVideoSector; import jpsxdec.modules.xa.SectorClaimToSectorXaAudio; @@ -96,6 +98,8 @@ public class SectorClaimSystem { scs.addClaimer(new SectorClaimToSectorAc3Video()); scs.addClaimer(new SectorClaimToSectorCrusader()); scs.addClaimer(new SectorClaimToDreddFrame()); + scs.addClaimer(new SectorClaimToPolicenauts()); + scs.addClaimer(new SectorClaimToRoadRash()); scs.addClaimer(new SectorClaimToUnidentifiedSector()); return scs; } @@ -111,9 +115,10 @@ public static abstract class SectorClaimer { abstract public void sectorRead(@Nonnull ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException; + throws IOException, ClaimerFailure; - abstract public void endOfSectors(@Nonnull ILocalizedLogger log); + abstract public void endOfSectors(@Nonnull ILocalizedLogger log) + throws ClaimerFailure; final public void setRangeLimit(int iStartSector, int iEndSectorInclusive) { _iStartSector = iStartSector; @@ -166,6 +171,12 @@ public String toString() { } } + public static class ClaimerFailure extends RuntimeException { + public ClaimerFailure(Throwable cause) { + super(cause); + } + } + /** The final sector after being processed by all claimers. */ public static class ClaimedSector { @Nonnull @@ -214,12 +225,13 @@ private SectorClaimSystem(@Nonnull CdFileSectorReader cd, int iStartSector, _outerMostIterator = new BufferedIOIterator(core); } - public void addClaimer(@Nonnull SectorClaimer claimer) { + void addClaimer(@Nonnull SectorClaimer claimer) { SectorClaimInception wrapping = new SectorClaimInception(_outerMostIterator, claimer); _outerMostIterator = new BufferedIOIterator(wrapping); _iterators.add(wrapping); } + @SuppressWarnings("unchecked") public @Nonnull T getClaimer(@Nonnull Class clazz) { for (SectorClaimInception iterator : _iterators) { if (iterator._claimer.getClass() == clazz) { @@ -239,7 +251,9 @@ public boolean hasNext() { return _outerMostIterator.hasNext(); } - public @Nonnull ClaimedSector next(@Nonnull ILocalizedLogger log) throws CdFileSectorReader.CdReadException { + public @Nonnull ClaimedSector next(@Nonnull ILocalizedLogger log) + throws CdFileSectorReader.CdReadException, ClaimerFailure + { try { _log = log; ClaimableSector next; diff --git a/jpsxdec/src/jpsxdec/modules/ac3/DemuxedAc3Frame.java b/jpsxdec/src/jpsxdec/modules/ac3/DemuxedAc3Frame.java index 2416ba7..49f959e 100644 --- a/jpsxdec/src/jpsxdec/modules/ac3/DemuxedAc3Frame.java +++ b/jpsxdec/src/jpsxdec/modules/ac3/DemuxedAc3Frame.java @@ -47,6 +47,7 @@ import jpsxdec.modules.video.IDemuxedFrame; import jpsxdec.modules.video.framenumber.FrameNumber; import jpsxdec.modules.video.sectorbased.SectorBasedFrameReplace; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.DemuxedData; import jpsxdec.util.Fraction; @@ -86,6 +87,10 @@ void setFrame(@Nonnull FrameNumber frameNumber) { return _frameNumber; } + public @CheckForNull MdecInputStream getCustomFrameMdecStream() { + return null; + } + public int getWidth() { return _iWidth; } public int getHeight() { return _iHeight; } public int getStartSector() { return _demux.getStartSector(); } diff --git a/jpsxdec/src/jpsxdec/modules/ac3/DiscIndexerAceCombat3Video.java b/jpsxdec/src/jpsxdec/modules/ac3/DiscIndexerAceCombat3Video.java index cf8adb5..b7da693 100644 --- a/jpsxdec/src/jpsxdec/modules/ac3/DiscIndexerAceCombat3Video.java +++ b/jpsxdec/src/jpsxdec/modules/ac3/DiscIndexerAceCombat3Video.java @@ -47,6 +47,7 @@ import jpsxdec.discitems.DiscItem; import jpsxdec.discitems.SerializedDiscItem; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.indexing.DiscIndex; import jpsxdec.indexing.DiscIndexer; @@ -129,7 +130,7 @@ public Ac3Channel(@Nonnull DiscIndexerAceCombat3Video indexer, int iChannel) { _sac3v2dac3frame = new SectorAc3VideoToDemuxedAc3Frame(iChannel, this); } - public void feedSector(@Nonnull SectorAceCombat3Video vidSector) { + public void feedSector(@Nonnull SectorAceCombat3Video vidSector) throws LoggedFailure { Ac3AddResult result = _sac3v2dac3frame.feedSector(vidSector, _indexer._errLog); if (result == Ac3AddResult.WrongChannel) throw new RuntimeException("AC3 sector was not accepted for some reason."); @@ -183,6 +184,7 @@ public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { public Ac3AddResult feedSector(@Nonnull SectorAceCombat3Video vidSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure { Integer oiChannel = vidSector.getChannel(); Ac3Channel channel = _activeStreams.get(oiChannel); diff --git a/jpsxdec/src/jpsxdec/modules/ac3/DiscItemAceCombat3VideoStream.java b/jpsxdec/src/jpsxdec/modules/ac3/DiscItemAceCombat3VideoStream.java index 13e219b..ee39ea1 100644 --- a/jpsxdec/src/jpsxdec/modules/ac3/DiscItemAceCombat3VideoStream.java +++ b/jpsxdec/src/jpsxdec/modules/ac3/DiscItemAceCombat3VideoStream.java @@ -46,6 +46,7 @@ import jpsxdec.discitems.DiscItem; import jpsxdec.discitems.SerializedDiscItem; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.DebugLogger; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.IIdentifiedSector; @@ -112,6 +113,11 @@ public DiscItemAceCombat3VideoStream(@Nonnull CdFileSectorReader cd, return TYPE_ID; } + @Override + public boolean hasIndependentBitstream() { + return true; + } + @Override public int getParentRating(@Nonnull DiscItem child) { if (!(child instanceof DiscItemXaAudioStream)) @@ -221,7 +227,7 @@ public void setFrameListener(@Nonnull IDemuxedFrame.Listener listener) { _listener = listener; } - public void frameComplete(@Nonnull DemuxedAc3Frame frame, @Nonnull ILocalizedLogger log) { + public void frameComplete(@Nonnull DemuxedAc3Frame frame, @Nonnull ILocalizedLogger log) throws LoggedFailure { FrameNumber fn = _frameNumberFormatter.next(frame.getStartSector(), _iEndFrameNumber - frame.getInvertedHeaderFrameNumber(), log); diff --git a/jpsxdec/src/jpsxdec/modules/ac3/SectorAc3VideoToDemuxedAc3Frame.java b/jpsxdec/src/jpsxdec/modules/ac3/SectorAc3VideoToDemuxedAc3Frame.java index 863a5d5..5702247 100644 --- a/jpsxdec/src/jpsxdec/modules/ac3/SectorAc3VideoToDemuxedAc3Frame.java +++ b/jpsxdec/src/jpsxdec/modules/ac3/SectorAc3VideoToDemuxedAc3Frame.java @@ -39,6 +39,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; /** Collects Ace Combat 3 sectors and generates frames. @@ -47,7 +48,8 @@ public class SectorAc3VideoToDemuxedAc3Frame implements SectorClaimToSectorAc3Video.Listener { public static interface Listener { - void frameComplete(@Nonnull DemuxedAc3Frame frame, @Nonnull ILocalizedLogger log); + void frameComplete(@Nonnull DemuxedAc3Frame frame, @Nonnull ILocalizedLogger log) + throws LoggedFailure; } private final int _iChannel; @@ -70,6 +72,7 @@ public void setListener(@CheckForNull Listener listener) { public @Nonnull Ac3AddResult feedSector(@Nonnull SectorAceCombat3Video vidSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure { if (vidSector.getChannel() != _iChannel) return Ac3AddResult.WrongChannel; @@ -89,7 +92,7 @@ public void setListener(@CheckForNull Listener listener) { return Ac3AddResult.Same; } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + public void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure { if (_currentFrame == null) return; if (_listener != null) diff --git a/jpsxdec/src/jpsxdec/modules/ac3/SectorClaimToSectorAc3Video.java b/jpsxdec/src/jpsxdec/modules/ac3/SectorClaimToSectorAc3Video.java index 6758543..3abe06d 100644 --- a/jpsxdec/src/jpsxdec/modules/ac3/SectorClaimToSectorAc3Video.java +++ b/jpsxdec/src/jpsxdec/modules/ac3/SectorClaimToSectorAc3Video.java @@ -41,6 +41,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.cdreaders.CdSector; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.IOIterator; @@ -50,8 +51,10 @@ public class SectorClaimToSectorAc3Video extends SectorClaimSystem.SectorClaimer public interface Listener { @Nonnull Ac3AddResult feedSector(@Nonnull SectorAceCombat3Video vidSector, - @Nonnull ILocalizedLogger log); - void endOfSectors(@Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void endOfSectors(@Nonnull ILocalizedLogger log) + throws LoggedFailure; } public static @CheckForNull SectorAceCombat3Video id(@Nonnull CdSector sector) { @@ -75,7 +78,7 @@ public void setListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { if (cs.isClaimed()) return; @@ -84,13 +87,25 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, return; cs.claim(vidSect); - if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) - _listener.feedSector(vidSect, log); + if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) { + try { + _listener.feedSector(vidSect, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } - public void endOfSectors(@Nonnull ILocalizedLogger log) { - if (_listener != null) - _listener.endOfSectors(log); + public void endOfSectors(@Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + if (_listener != null) { + try { + _listener.endOfSectors(log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } } diff --git a/jpsxdec/src/jpsxdec/modules/dredd/DreddFrameToFrame.java b/jpsxdec/src/jpsxdec/modules/aconcagua/AconcaguaDemuxer.java similarity index 63% rename from jpsxdec/src/jpsxdec/modules/dredd/DreddFrameToFrame.java rename to jpsxdec/src/jpsxdec/modules/aconcagua/AconcaguaDemuxer.java index af55869..af0c037 100644 --- a/jpsxdec/src/jpsxdec/modules/dredd/DreddFrameToFrame.java +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/AconcaguaDemuxer.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2017-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -35,39 +35,38 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package jpsxdec.modules.dredd; +package jpsxdec.modules.aconcagua; -import javax.annotation.CheckForNull; +import java.util.List; import javax.annotation.Nonnull; +import jpsxdec.modules.video.sectorbased.*; import jpsxdec.i18n.log.ILocalizedLogger; -import jpsxdec.modules.video.IDemuxedFrame; -/** Converts a Dredd frame to a generic frame. - * Necessary to capture details about a Dredd frame before - * passing it off as a generic frame. */ -public class DreddFrameToFrame implements SectorClaimToDreddFrame.Listener { - @CheckForNull - private IDemuxedFrame.Listener _listener; +public class AconcaguaDemuxer extends VideoSectorWithFrameNumberDemuxer { - public DreddFrameToFrame() { - } - public DreddFrameToFrame(@Nonnull IDemuxedFrame.Listener listener) { - _listener = listener; - } - public void setListener(@CheckForNull IDemuxedFrame.Listener listener) { - _listener = listener; - } + private final int _iQuantizationScale; - public void frameComplete(@Nonnull DemuxedDreddFrame frame, @Nonnull ILocalizedLogger log) { - if (_listener != null) - _listener.frameComplete(frame); + public AconcaguaDemuxer(@Nonnull SectorAconcaguaVideo firstChunk, + @Nonnull ILocalizedLogger log) + { + super(firstChunk, log); + _iQuantizationScale = firstChunk.getQuantizationScale(); } - public void videoBreak(@Nonnull ILocalizedLogger log) { + @Override + public boolean addSectorIfPartOfFrame(@Nonnull ISelfDemuxingVideoSector sector) { + if (!(sector instanceof SectorAconcaguaVideo)) + return false; + if (((SectorAconcaguaVideo)sector).getQuantizationScale() != _iQuantizationScale) + return false; + return super.addSectorIfPartOfFrame(sector); } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + @Override + public DemuxedAconcaguaFrame finishFrame(@Nonnull ILocalizedLogger log) { + @SuppressWarnings("unchecked") + List s = (List)getNonNullChunks(log); + return new DemuxedAconcaguaFrame(getWidth(), getHeight(), getHeaderFrameNumber(), s, _iQuantizationScale); } - } diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/BitStreamUncompressor_Aconcagua.java b/jpsxdec/src/jpsxdec/modules/aconcagua/BitStreamUncompressor_Aconcagua.java new file mode 100644 index 0000000..53bc253 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/BitStreamUncompressor_Aconcagua.java @@ -0,0 +1,530 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.bitstreams.BitStreamDebugging; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; +import jpsxdec.psxvideo.mdec.MdecException; +import jpsxdec.psxvideo.mdec.MdecInputStream; +import jpsxdec.util.IO; +import jpsxdec.util.Misc; + +/** + * The Aconcagua video bitstream decoder. + *
        + * nnnn##+++++++++++++++++++*;:,:;*znnnzzz#################zzzzzzzzz*;,`   ``...``   `.,:,,.```.,:i*+znzznnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn#####++++++++++++++++*;:,:;*znnnnzzz###############zzzzzzzzn#*:.`   ``.,.`    `.,,,.`````.:;i+znnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnxnnnnnn
        + * nnnn####+++++++++++++++++*;:,:;*znnnnzzz###############zzzzzzznn#i:.`   `.,,.`     `.,,.`` ``.,;i*#nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn####+++++++++++++++++*;:,:;*znnnnzz###############zzzzz#####+i:...``.,,,.`     `.,,.`` `..,:;i+++nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn####+++++++++++++++++*;:,:;*znnnnzz###########zzzzzzzz+**i*++*****#zzzz+i,`   ``.:,.````..;::ii;:+nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn####+++++++++++++++++*;:,:;*znnnnzz##########z#zzzzz#**;;i*+zxMM@W@@@@WWWni.` ``,:,,.```.;;i;ii;;;+nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn####+++++++++++++++++*;:,:;*znnnnzzz#########zzzzzz#+*;:;i#nMMW@@W@@@@@@@@Wx+.``,::,.```.*iii***;;i*nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn###++++++++++++++++++*;:,:;*znnnnzz#####z####z+zzzz+*;::innMMWMWxM@####@@@@@Wx+::::,.``,.*+*ii;;*i;i+nnznnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn#++++++++++++++++++++*;:,:;*znnnnzz###;;*###zi;;*z#*i:;*xxMWMWWWWW#@@@@@@@@@@WWMzi:,.`;;;:+#*ii;;ii;iznnzznnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        + * nnnn#++++++++++++++++++++*;:::;*znnnnzz#z;;;i#z+:;;;iz+i;;+MxxMMW@W@W@@@@@@@@@@@@@WMMx+:.`+;;;i*++*ii;;i:inznzzzzznnnnzzzzznnnnnnnnnnnnnnnnnnnnnnznnnn
        + * nnnn#++++++++++++++++++++*i::;;*znnnnzzz*i;i*+;i*;;i*z+i#znMMxWW@@##@@####@@#@#@@@@WWWWn:`**i;;i*+z+*i;;i:*nzzzzzzzzzzzzzzznnnnnnnnnnnnnzzznnnnnnznnnn
        + * nnnn#++++++++++++++++++++*i;;;;*#nnnnzz#**;*#+*ii;i*#nnxxxxWW@@@#@#@@####@@@@@@@@@@@@WW@M;.##ii;ii*++*i::i;inzzzzzzzzzzzzzzznznnnnnnnnnzzzznnnnnnznnnn
        + * nnnn#+++++++++++++++++++++i;::;i+znnnzz**i*#+*i;;**#Wz+M@@@W@@@@#@@@@@@@@@@@@@@@@@@W@@WMWM*.i#+*;;ii*++i::iizzzzzzzzzzzzzzzzzznnnnnnnzzzzznnnnnnnnnnnn
        + * nnnn#+++++++++++++++++++++i;,,:;*znnnn#+ii+**iiii*zM+;:+@@@@@@@##@####@@@@@@@##@@@@@@WMMMWM*..i#*i;;ii+#*;i*+zzzzzzzzzzzzzzzzzzzzznnzzzzzzzznnnnnnnnnn
        + * nnnn##++++++++++++++++++++i:,.,:i#nnnz+**++ii;i*+zzi;;;*@@@##########@@#####@@@@@@@@@WxxMMxxz,`:#*;;:;i++i;*inzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnnznnnn
        + * nnnn##+++++++++++++++++++*;,...,i#nnn#****i;:;i+z+iii;i#@@########@##########@@@@@@@@@WMWMMxxz,.:#+i;:;i++ii*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*;,``.,;#nn#*iiii;:;*+#*i;;;i+M@###################@#@#@@@@#@@@W@WMMW*..:++*;;i*+*i*+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn###++++++++++++++++++*;,```,;+nz**i*i;:;**#*i;;;i#W#####################@@@@##@@##@@@W@WW@x:,,;+#*;i*++***nzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn###++++++++++++++++++*;,```,i#n++*+ii:;+#+i;;;i*z@##################@@@@@@@@@###@@@@@@@@@@@+.,:i+#i;i*+***zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn###++++++++++++++++++*;,``.,i#z+++*i;;+z*i;:;i+M@##########################@##@@@@@@@@#@@@@M:,:;**+i;*****zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn###++++++++++++++++++*;,.`.:i#+++**;;*#*i;::i*M#######################@@###@##@@@#@@@@MznW@@*,:;**+*;i****+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn####+++++++++++++++++*;,...:*z+***i:*+*i;::i*n@######@#############@@@@##@@###@#@@@@#@z::;i*+:,;*++*;i*****nzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn####+++++++++++++++++*;,..,;+#+**i;i#*i;::i*n@########@@########@@@###@@@@#@@@@@@@@@##n::;i;iii;*z+*i;i***izzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn####+++++++++++++++++*;,,,,i#+++*;;#++i;;i+n@#@#####@@@@@@######@@@#@##@@#@@@#@@##@@##W+i;;;;i;i*+z*i;ii*+**zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn##+++++++++++++++++++*;,,,:i+***i;*#*ii;i*z@@@###Wxn@@WWWW###@@@@@@@@@@@@@@@@@#@#######Wz*iii;;;ii*+*;;i*i*izzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn###+++++++++++++++++++;,,,,i*+**ii#*i;;i*iM@##Wx#;::x@WWMWW@@@@@@####@@#@@@@#@@@########@Wz*ii;;;i*+*i;;*i;i*zz#zzzzzzzzzzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn##+++++++++++++++++++*;,,,,;****;++i;;;*i+@#@xiii:::nWWWMMMW@@@##@@@@@@@@@@@@@@###########@Wzii;ii****i;i**ii#zz#z#z########zzzzzzzzzzzzzzzzzznnnn
        + * nnnn#+++++++++++++++++++++;,,,,i+*iii+ii;;**ix#W#i;;;;;iMWMMMMxMWW@@@@@@@@@@@@@@@@@@###########@@W#*iiii***ii***iiz#zz#############zzzzzzzzzzzzzzznnnn
        + * nnnn#+++++++++++++++++++++;,,.:+*iii+*i;;*+*#Wni;i;;;;*x@WWWMxznxxWWW@@@@@@@@#@@@@@@@@##########@@M++*i;ii***ii*i;*z#z##########zzzzz##zzzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*;,.,*iiiii*ii;i+*#z*i;:iiii#W@WWWWxn#zzznnxMM@@@@@@@@@@@##@@@###########@;*+*iiiii******izz###################zzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*;,.iii*i**i;;;i**+*ii:;;i+M@@@@@WWWz+#nn###xxMW@@@@@@@@@@#@@#@########@@#ziz#*i;i*i****ii*z#####################zzzzzzzznnnn
        + * nnnn#+++#++++++++++++++++*;,:******iii;ii+**iii;;;#@@@@@@WWMMnz##zzz++zxMWWW@@@@@@@@@WMWW@@@###@@@@M*z+i;;;ii*ii*ii;######################zzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*;,;***i**ii;;;**iiii;;;z@@#@@@@@WMnnz#+####++##xWWWWW@@W@@@WxnnxxW######@W*##*;;;ii*iii+*ii#####################zzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*i:******iiiii**ii;;;i*x@@@#@@@@WMnnnz+*+#++*iii++xxnM@WMnMMzzzzznn@@@@@##@+*++i;;iiiiii*+i:+###################zzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++*ii**+i*iii***ii;;;;iin###@@@@@@Mxxzznz#+++iiii;+iinz#nMnz#z#znnnnnM@@@@##@##Mn+;;;iiiiii**:;###################zzzzzzzzznnnn
        + * nnnn#+++++++++++++++++++++i+****i***ii;;;;;ii*x@###@#@@WMnnnzznnz#*#+**+i+i:++*##zznznxxxnnnx@@@@##@#xWxn+i;iiiiiiiii;*#############zzz#zzzzzzzzzznnnn
        + * nnnn#+++++++++++++++++++++*********ii;;;;;i*zW@######@Wxzzznzzznnzzxnznz*+i;*#i+zzxMMnnxnn#nnW#@@####M@Wzi;;;iiiiii*i*;###############zzzzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++++*+**i+*iii;;;;;;*M######@@WMzzznnzz#z#zxnnnz##+*ii#++znnnnnnnzz#znM@#####zn@@n*;;;;ii;;ii*ii+#############zzzzzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++***+*i**iiii;;;;ii#@#####@@@Wnzzznz####znn##++zz##+**#z#+iiii*#zz#znx@#####z#nWM*i;;;;iii;i***i+z###z#####zzzzzzzzzzzzzzznnnn
        + * nnnn#++++++++++++++++++++**+*i**iiiiii;;;i*W#######@Wxzzzzn#####+ii*+**++#+ii*#+i*#zz###+z#znx@####@z#zzx#i;;:;ii;;ii*++i#z#zzz###zzzzzzzzzzzzzzzznnnn
        + * nnnn#+++++++++++++++++++*******ii;;;;;;;;ix@######@@Mnzzzzz###+*+znnxn##+#+ii+#++znxW@WMn###nnW####W#z#zz+ii;;;i;;;iiii;iizz###zzzzzzzzzzzzzzzzzznnnnn
        + * nnnn#++++++++++++++++++*******i;;;;ii;;i+M########@@Mnzzzzz+++#xMW@Wzxz+++#***##*z#+#z#zx###nzW####x#zzzz#iii;;;;i;;;;ii,:*z#z#zzzzzzzzzzzzzzzzzznnnnn
        + * nnnn##+++++++++++++++++**+***ii;;;;;;;;;*@########@@xzz##z#+++nz+++**++**##+**#z*+++**i+#z##zzM###@##zzzzz+*iii;;;;;i;;i;,:+zzzzzzzzzzzzzzzzzzzzzznnnn
        + * nnnn####++++++++++++++*i*+**iii;;;;;;;;i+W########@Wxz#####+##+*iii*+##++#+***#z#*+*++*+++#+zzM###W+#zzzzz#iii;;;;;;;;;;i:::+nzzzzzzzzzzzzzzzznnnnnnnn
        + * nnnn####++++++++++++++*****i;;;;;;;;;;i*#M#########Wxzzz###+++**********+#+***###*iiii****++#zM##@x+#zzzzzz*iii;ii;;;:;;;i;;;+nzzzzzzznzzzzznnnnnnnnnn
        + * nnnn####+++++++++++++*i***ii;;;;;;;;;;i+#z@#######@Mnzz###+++***iiiiiii*+++***+##+*iiii**+++##x##@#+##zzzzz#****i;;;:::;;;*i;;#nzzzzznnnnnnnnnnnnnnnnn
        + * nnnn####++++++++++++*i**iiii;;;;;;;;;i*###@#@@####@xnz####+++***iiiiii*+++++**###+***iiii*++##x#@@*+##zzzzzz#**iii;;::::;;iii;;znnnzznnnnnnnnnnnnnnnnn
        + * nnnn##++++++++++++++***iii;;;;;;;;;;ii*##+x#@@@##@@xnz###++***iii;ii*****+++**+##++***iiii*+##x##@i+zzzzzzzzz#*++*i;:::;;;;ii;:;nnznnnnnnnnnnnnnnnnnnn
        + * nnnn#+++++++++++++*i*i*i;i;;;;;;;;;;i*###+z@@@@@@@@xnz####**iii;;i***iii+++*ii*+#+****iii***##x##x;+zzzzzzzzzzz#++i;:::;:;;i*i::innnnnnnnnnnnnnnnnnnnn
        + * nnnn##+++++++++++i****iii;;;;;;;;;;i**###++W#@@@@@@xzz####+*i;;;iiiiiii*++*ii;i*##*i*iiii*++##x#W+i##zzzzzzzzzzz##*;;;:::;;ii*i;;innnnnnnnnnnnnnnnnnnn
        + * nnnn##++++++++++i****i;;;;;;;;;;;;i**+z##++n@WMW@@WMzzz#+#+*ii;;;;iiiii+#+*i;:;*+##*ii;ii*++##xW#;i+zzzzzzzzzzzzz#+ii;;:::;;;iii;:*nznnnnnnnnnnnnnnnnn
        + * nnnn##+++++++++******i;;;;;;;;;;i***+zz##++z@WMW@@Wxzz####+**i;;;;;ii;*#++*;;;;i+##+iiiii*+#####i:i#zzzzzzzzzzzzzz#+*;;;;:;;;i;i;;:+nnnnnnnnnnnnnnnnnn
        + * nnnn##+++++++#+*****i;;;;;;;;;;ii**+zzz##+++WMWWWWMnz#####++**i;;;;;;*#+***iiii*++##*iiii*+###z+::i#zzzzzzzzzzzzzn##+i;;::;;;;i;;;::znnnxnnnnnnnnnnnnn
        + * nnnn###+++++#+*i****i;;;::;;;iii**+znzz##+++xMMWWWzzz#####++***ii;;;i+#+****iii*#++#+ii***+####+,:i#zzzzzzzzzzzzzzz#+*i;;;;;;;;i;;;::#+;;*nnxnnnnnnnnn
        + * nnnn###+##+#+ii****i;;;;;;;;;***++znzz###++*#MxW@W#zzz#####++**iiiii**+##xM#+*+zMn#++*i***+##zii,:i#zzzzzzzzzzzzzznz++i;;;;;;;;;i;;::;+i;:i:*#nnnnnnnn
        + * nnnn####i##+******ii;;;;;;;ii+##+zzzzzz##++*ixxMMWn#zz#z#++++***iiii*ii+##zzzz##zz#+*****+#+##*:.:i#zzzzzzzzzzzzzzzn#++i;;;;;;;;ii;;;:+#i:;:ii*#nnnnnn
        + * nnnn##+:,i+*i****ii;;;;;;;;i*#i:*#zzzzz##++*;zMxxnzz#zzz##++++**iii*iii*****++**+++++****+#+##*;.,i+zzzzzzzzzzzzzzznz++*ii;;;;;;;ii;;;+nx*:;;:;+nznnnn
        + * nnnn#+*ii:+i*****i;;;;;;;;i*#+,:i#nzzzz##++i;*Mxn+#z#zzz#+++++***i*iiii*****ii**+++*++**++###++:`,;+zzzzzzzzzzzzzzzzz#+**i;;;;;;;;ii;;+zxzii;:;znznnnn
        + * nnnn#+;i+n#****ii;;;;;;;;i*+#,,:i#zzzzz##++i;;Mxz+##zzz##++*++*****iiii*****iii*+++*++++++#+#+*``,;+#zzzzzzzzzzzzzzznz+**ii;;;;;;;iiii+#nx+ii;innnnnnn
        + * nnnn##*;i#+****i;;;;;;;;ii*#;,,:*#nnzzz##+*i;:x@z+##z#####+**+******iii*i*******++++++++++#+#i` `.:*#zzzzzzzzzzzzzzzzz#++*iii;;;;;iii*+zz#i:;ii#nnnnnn
        + * nnnn##**;++++*ii;;;;;;;;i*++::,:*#zzzzz##+*i;,##@xnMx###z#+**********ii***++**+#+++++#+++++##,` `.:i#zzzzzzzzzzzzzzzzzz+***iii;;;;;;i;*;;,;ii*;;nznnnn
        + * nnnn#i;i+;*#+*ii;;;;;;ii*++i,,,:i#zzzzz##+*i:,*@##@@Mzz#z#+*****i***+ii**+########+*+#+++++#+,`  `,;+zzzzzzzzzzzzzzzzzn#**ii;i;,:;i:;,;;,:::;ii:+nnnnn
        + * nnnni;ii*i*#+*i;;;;;;ii*+++;,..,;+zzzzz##+*i::;W@##@xz#z##++*********ii*#zznnzxnzz#++++++++#*:.  `.:+zzzzzzzzzzzzzzzzz+;i+**i;*ii:iii*;:,::;;ii;;znnnn
        + * nnnnii;iii+#+**i;;;;ii**++*;,..,:*#zzzz##+*i:,,z##@Mxzzzz#++***********#nxnnxxxnnn##+++++++#i:,. `.:*#zzzzzzzzzzzzzzzz*ii#*i***+ii;**;;;;;;;;;;i:#nnnn
        + * nnnn*i**iiz#++*i;;;ii**+++*;,``.:i#zzzz##+*i;,,;W#@zzzzz##++**********+*+z#+###+#z+#+++++++#n;,,` `,;+zzzzzzzzzzzzzzzzzz+*********i*ii:;;;;iii;;;#nnnn
        + * nnnn***;:*##+***i;iii*++++*:.```,i#nzzz##+*i;:,,+#+i+zzz####*******ii**++##+***+##+++++++++xx+,..``.:*#zzzzzzzzzzzzzzzzz+i*ii*******;;;;;::;;iiii#nnnn
        + * nnnn**i:i+#+++**iii**+++++*:.` `,;#nzzz##+*i;:,,,,,:*#zz####++******ii**++###+###+*+++++++#@M#i,:.``,;*#zzzzzzz#########+iiii******ii;;i;:;;;;;ii#nnnn
        + * nnnn+i;i*;+++******+++++*+*:.```,;#nzzz##+*i;:,,,,,;i+zzz####+****+*******+#zzz#++*+++++++M@Mz#;:,` `,;*+###+++****iiiiiii*iii******iii;;;;;iiii*#nnnn
        + * nnnn*;i*ii*******+++++#*i+*;.```,i#nzzz##+*i;::,::;*i+########+*+++********+###+**++++#++#@@Mz#*:,,` `,,;ii;::;;;:::,,::;*iiiii******iiii;;;;;;;iznnnn
        + * nnnniiiiii;;****+++++#i*i+*;,...:i#nnzz##+*i;;:;i++*i;########++++#+***+**+*+++*++++*++++n@@xz#+i,.````,;;;;;::;;:,...,:;iii*ii*******iii;ii;i;;;znnnn
        + * nnnn*ii;;ii*+*iiii+***+*++*;,...:*znzzzz##**;;i*++*i*;+########+*+#+***++***i******+*+#+#M@@xz#++;``.`:i;i*;;;iii;:::;i*+iii*ii****+***ii;;;;i;i;#nnnn
        + * nnnn*i;;;;i*+#+i**++##+#++*;,..,;+znnn###+*i:i**#+iii*i+#+#####+++#+****i*iii;ii**i**+#+#M@Wx###+i,..;*i***ii;i;i;:;**++z*iiiiiii*++***iiiiii;;ii#nnnn
        + * nnnn*i;;iii****++###z++#++*;,,,:i#nnzi;;:;;;*z+++*;i**i;+####z##++##+ii*iiiiii;ii*i**###zM@Mn##++;::.i+*+++ii;;ii;i+++#nn*i;;iiii*******i;;;i;;;;#nnnn
        + * nnnn*;;;iii*i*++*++++*i*++*;:::::;#z**+ii;;i+n#*+;;i**iii*#######+##+*iii;;;;i;;ii*++###zWWxz###*i;::;*++#+**;i+*i*#++#n*ii;;;iii***i****ii;;;;;;#nnnn
        + * nnnniiiiiii***+*+*+*+*i::;:,:::;:::i+z#*+**i+z#*+;i****ii;i+#########+*iiiiiiiii***++###zWMnz###*;;i;i+#+++i**i#+i*z+#n**z*i;;;;iii**i+*ii;;;i;;;#nnnn
        + * nnnnii;i**ii*++***+***+i::;;;;iii*i:izz++#+*i+#+*i*******i;i*zz####+#++************+####zMMn####*;;i**+++++***;z+i+z+n+*#z*i;;;;;ii**i**iii;;;;;;#nnnn
        + * nnnni;;ii***+****++***#*;:;;ii******;*n#*+z+i;#+*i*ii******i*#zz######+++********++####zzxMz#+++iiiii#++zz+***i##i#zzz*#+++ii;;;;;iii*i*iiiiii;;;#nnnn
        + * nnnn*i;iii**++***++***#*i;:;i*+**+++ii+z++z#i;i++**i******ii+i#znzz##+#+#++**+++++####zz#nnn#+++ii;i*ii+#z*****#z*z#n++++z#iiii;iiiiii**i*ii;;;;;#nznn
        + * nnnnii;ii*+******+****n*ii;:;i****++**i+#+z#*i;i++*iii***+***i*#nnzzz##++++++*++++#####z+z#z#++i;ii+*i**+zii***#z*zz++++#+**iii;;iii*i**i*ii**iii#nnnn
        + * nnnniiii***i****++****x+ii*i;:i***+++*i+++z#+i;;i+*ii;ii*++**iiznnz########++##+######z*#####++ii**+***++#i;*i*+n*zz*+++**+i*i;;;;iiii****iiiiii;#nnnn
        + * nnnni;;i*i******+***+*x#*i*+*i;***+++*i**+#z#+i;i***iii;****+*;*+xn#########zz####+##zi;z#####+i;i*+**+*++;:i*i*z+z#*++*+***i*;;i;;iii*****;ii;;i#nnnn
        + * nnnn*ii**i***+*+***i**x#*i****i***+++*i****z#+i;;i*i;ii;;i****;i+zxzzz######++######+;,*z++#++*ii+nz**+*+*;;i***+#z+****+*++iii;;i;i*******i*ii;;#nnnn
        + * nnnn*ii*ii*i+*++******x#*i*********++****i*#z#+i;ii;;iii;;iii*i+++nz##++++**++++++*;..:+#+++*iii*++*,**i+i;;:****##***++***+*i**;iii;***++*ii;;;i#znnn
        + * nnnn*****i**+*+******+W#*i*********#+**i**++##+*;;i;;;;ii;;ii;i#z###+#####++*i;:.```..i++**;;ii*iii:.:*i+*;;;ii**#z+***+***##i*ii;iiii**+****i;;;#nnnn
        + * nnnn+****+*+*++*****i+@z*i*********#+*ii****+#+i;;ii;;;iii;iii;+*;........````  ```..,iiii;i;iiiiii:.:***i;;i**i*+n+**++**+#+**iiiiiiiii*+***iiii#nnnn
        + * nnnn***++****++*****i+Wz*i**i+*i***##**i*****+*ii;;i;;;;;iii;i;+*:.`             ``..,,;i;i;;i;iiii:.i***iii;i*i*+n#**+***++******ii;iiiii*+**iii#nnnn
        + * nnnn********++***+***+xz**+*******+##*i*i**i***ii;;ii;;;i;iii;i*+:.             ``...`,iii;;;ii;ii*:.i*iii;i;i+ii*zz*********i****i**i;ii*******i#nnnn
        + * nnnn*ii**ii**********+nz+*********+##***i**ii;i*i;:;i;;;iii;ii*zz#i.`    `````````````:ii;;;;;;;iii:.i**iiii;;*i;*#n****i++*iii*******iii********zznnn
        + * nnnn*;;;:ii*;;i;**i;;;i#+******i**+z#**i;i*i;;iii;;;ii;;;;i;;;*++#*:.``   `     ``````ii;;;;i;;;;ii:,***iiii;i*i;i+n+******iiiiii*i*************iznnnn
        + * nnnn+;;;,i;;i;:;;i;;:;*z*i*****i**+z+*i;;i*i;;iiii;;;i;::;iiiii;;;:.``          `````.*;;;;;:;;iii;,,i***ii;;;**;;inz******;;;iiiiiii*********+*i#nnnn
        + * nnnn*ii**+**ii**i;i+ii+z+***i*****+z+*i;;;*i;;;iii;:;i;:;i;;iiiiii,..```       `` ``..*;i;;;:;;;ii;,,***ii;::;**i;;#n*+***i;;;iii;ii;iii*****++**#nnnn
        + * nnnnii*++++****iii****+z+***i*****+z+;ii;i;;i;;ii*;;;ii;;;;i;;i;ii,.,.``      `` ` .`.;i;::;;:;;ii;:;****ii::;**i;:ix+****i;i;iiii;;i;;ii*i*i**++zznnn
        + *
        + * + * For each block + * - Read the DC code (normal) + * - The bits either point to a predefined VLC lookup table (normal) + * - Or there is an escape code indicating the DC value is in the + * biitstream itself (normal) + * - The chroma DC values are relative to prior chroma values (normal) + * - The luma DC values are relative to prior luma DC values (normal) + * - The choice of crisscrossing which luma block DC codes are relative + * to others is... (odd) + * - Now you must read an instruction code that tells us how we are going to + * decode this block (wait... what?) + * - There are only 64 predefined list of instructions, and there is no + * escape code to offer an alternative number (that would mean.. how?) + * - The instruction will tell us how many AC VLC codes to read from 3 + * more different tables (and 3 more??) + * - So you follow the instructions, and read X VLC codes using the first + * table, Y codes using the second table, and Z codes using + * the third table (uh, sure) + * - Each table comes with it's own escape code to allow for entries not + * in the lookup table (back to some normalcy) + * - After following the instructions, the block is over (ok) + * - The bit reader used for reading the bit stream is actually pretty cool. + * The bits are read backwards from how normal bitstreams work, which makes + * the reader extremely simple and likely pretty fast. (cool) + * - And after reading one vertical column of macroblocks, you reset all the + * DC values to 0 (alright, that's fairly reasonable) + * and you skip up to 4 bytes in the original bitstream (4 bytes just + * wasted??) + * + * What even....... + * + * There's no escape code to do some unique number of table lookups among the + * three tables. You're stuck with them. Which means there will be cases where + * you really don't need to read N values from a table--too bad, because you're + * reading that preset count no matter what. Or maybe it would be better to read + * more from one of the tables to save some bits--nope! Or maybe you need to + * read only N number of codes in a block, but there is no X+Y+Z=N. What do you + * even do??? + * + * Now consider this from the perspective of the insane encoder someone had to + * write. How do you choose how many values need to be read from each table. And + * even more insane, how were the final instruction table values chosen in the + * first place????? And why are there 3 tables to read from???? Why not 2? Or + * 4? And how were the values of those tables even chosen as well???????? + * + * Now let's say maybe all this somehow actually saved some space in the frame + * to allow for a better quality image. Those few *BIT* savings are completely + * blown away by wasting at least a dozen *BYTES* per frame. + * + * And all this insanity is compounded by the fact that there is the standard + * PlayStation bitsream formats that + * - are readily available + * - encode better + * - are better quality + * - decode faster + * - and the decoding logic is already written for you + * - and so is the encoder!! + * + * Having said all that, we all make poor design decisions in our lives. Trying + * to spin up your own version of some other program because you could do it + * better--only to be wrong, but you're stuck with it now. It happens to all of + * us. I'm sure someone in the devolpment team wished they could go back and do + * it differently. + */ +public class BitStreamUncompressor_Aconcagua implements MdecInputStream { + + private static final int MACROBLOCK_HEIGHT = 13; // 208 / 16 + + private final int _iQuantizationScale; + @Nonnull + private final MdecContext _context; + + private int _iBlocksLeftInColumn = MACROBLOCK_HEIGHT * 6; + + public BitStreamUncompressor_Aconcagua(int iMacroblockHeight, int iQuantizationScale, @Nonnull byte[] abBitstream) { + _iQuantizationScale = iQuantizationScale; + _bitstream = new AconcaguaBitReader(abBitstream, 0); + _context = new MdecContext(iMacroblockHeight); + } + + // ------------------------------------------------------------------------- + @Nonnull + private final AconcaguaBitReader _bitstream; + private int _iPreviousDcCr = 0; + private int _iPreviousDcCb = 0; + private int _iPreviousDcSetInY1UsedInY2Y3 = 0; + private int _iPreviousDcSetInY3UsedInY1Y4 = 0; + + private int _iTable1Reads = -1; + private int _iTable2Reads = -1; + private int _iTable3Reads = -1; + + private void resetColumn() { + _iPreviousDcCr = 0; + _iPreviousDcCb = 0; + _iPreviousDcSetInY1UsedInY2Y3 = 0; + _iPreviousDcSetInY3UsedInY1Y4 = 0; + _bitstream.resetColumn(); + _iBlocksLeftInColumn = MACROBLOCK_HEIGHT * 6; + } + // ------------------------------------------------------------------------- + + private static final int BOTTOM_3_BITS = 0x0007; + private static final int BOTTOM_8_BITS = 0x00ff; + private static final int BOTTOM_9_BITS = 0x01ff; + private static final int BOTTOM_10_BITS = 0x03ff; + private static final int BOTTOM_11_BITS = 0x07ff; + private static final int BOTTOM_14_BITS = 0x3fff; + private static final int BIT9 = 0x0100; + private static final int BIT10 = 0x0200; + + public boolean readMdecCode(@Nonnull MdecCode code) throws MdecException.EndOfStream, MdecException.ReadCorruption { + if (_context.atStartOfBlock()) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(_context.toString()); + if (_iBlocksLeftInColumn == 0) + resetColumn(); + _iBlocksLeftInColumn--; + + int iNext32Bits = _bitstream.getBits(); + int iBitsToSkip = readDc(_context.getCurrentBlock(), code, iNext32Bits); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("%d bits qscale/dc %s", iBitsToSkip, code)); + _bitstream.skipBits(iBitsToSkip); + _context.nextCode(); + return false; + } else { + if (_context.getMdecCodesReadInCurrentBlock() == 1) { + + int iInstructionCode = _bitstream.getBits() & BOTTOM_10_BITS; + InstructionTable.InstructionCode instruction = InstructionTable.lookup(iInstructionCode); + _bitstream.skipBits(instruction.getBitCodeLen()); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("%d bits %s", instruction.getBitCodeLen(), instruction)); + + _iTable1Reads = instruction.getTable1Count(); + _iTable2Reads = instruction.getTable2Count(); + _iTable3Reads = instruction.getTable3Count(); + } + + if (_iTable1Reads > 0) { + int iNext32Bits = _bitstream.getBits(); + int iBitsToSkip = readTable1(code, iNext32Bits); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("%d bits %s", iBitsToSkip, code)); + _bitstream.skipBits(iBitsToSkip); + _iTable1Reads--; + _context.nextCode(); + return false; + } + + if (_iTable2Reads > 0) { + int iNext32Bits = _bitstream.getBits(); + int iBitsToSkip = readTable2(code, iNext32Bits); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("%d bits %s", iBitsToSkip, code)); + _bitstream.skipBits(iBitsToSkip); + _iTable2Reads--; + _context.nextCode(); + return false; + } + + if (_iTable3Reads > 0) { + int iNext32Bits = _bitstream.getBits(); + int iBitsToSkip = readTable3(code, iNext32Bits); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("%d bits %s", iBitsToSkip, code)); + _bitstream.skipBits(iBitsToSkip); + _iTable3Reads--; + _context.nextCode(); + return false; + } + + code.setToEndOfData(); + _context.nextCodeEndBlock(); + return true; + } + } + + private int readDc(@Nonnull MdecBlock block, @Nonnull MdecCode code, int iNext32Bits) throws MdecException.ReadCorruption { + boolean blnIsDcEscapeCode = (iNext32Bits & BOTTOM_3_BITS) == 0; + + int iDcFromEscapeCode = -1; + int iRelativeDcFromTable = -1; + int iBitsToSkip; + + if (blnIsDcEscapeCode) { + iDcFromEscapeCode = (iNext32Bits >> 3) & BOTTOM_10_BITS; + iBitsToSkip = 3 + 10; // 3 zeros + 10 bits for DC + } else { + DcTable.DcCode entry = DcTable.lookup(iNext32Bits & BOTTOM_11_BITS); + iRelativeDcFromTable = entry.getRelativeDcCoefficient(); + iBitsToSkip = entry.getBitCodeLen(); + } + + int iFinalDc; + + switch (block) { + case Cr: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcCr; + _iPreviousDcCr = iFinalDc; + break; + case Cb: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcCb; + _iPreviousDcCb = iFinalDc; + break; + case Y1: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcSetInY3UsedInY1Y4; + _iPreviousDcSetInY1UsedInY2Y3 = iFinalDc; + break; + case Y2: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcSetInY1UsedInY2Y3; + break; + case Y3: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcSetInY1UsedInY2Y3; + _iPreviousDcSetInY3UsedInY1Y4 = iFinalDc; + break; + case Y4: default: + if (blnIsDcEscapeCode) + iFinalDc = iDcFromEscapeCode; + else + iFinalDc = iRelativeDcFromTable + _iPreviousDcSetInY3UsedInY1Y4; + break; + } + + code.set((_iQuantizationScale << 10) | (iFinalDc & BOTTOM_10_BITS)); + + return iBitsToSkip; + } + + private static int readTable1(@Nonnull MdecCode code, int iNext32Bits) throws MdecException.ReadCorruption { + if ((iNext32Bits & BOTTOM_8_BITS) == 0) { + // escape code + + int iMdecCode; + int iVlcCodeLength; + if ((iNext32Bits & BIT9) != 0) { + // positive + iMdecCode = iNext32Bits >> 9; + iVlcCodeLength = 25; + } else { + // negative + int iSignExtendAc = (iNext32Bits << 16) >> 25; + iMdecCode = iSignExtendAc & BOTTOM_10_BITS; + iVlcCodeLength = 16; + } + + code.set(iMdecCode); + return iVlcCodeLength; + + } else { + ZeroRunLengthAcTables.AcCode ac = ZeroRunLengthAcTables.lookupTable1(iNext32Bits & BOTTOM_14_BITS); + ac.setMdec(code); + return ac._sBits.length(); + } + } + + private static int readTable2(@Nonnull MdecCode code, int iNext32Bits) throws MdecException.ReadCorruption { + if ((iNext32Bits & BOTTOM_9_BITS) == 0) { + // escape code + + int iMdecCode; + int iVlcCodeLength; + if ((iNext32Bits & BIT10) != 0) { + // positive + iMdecCode = iNext32Bits >> 10; + iVlcCodeLength = 26; + } else { + // negative + int iSignExtendAc = (iNext32Bits << 17) >> 27; + int iZeroRunLength = (iNext32Bits >> 5) & 0x1c00; + iMdecCode = (iSignExtendAc & BOTTOM_10_BITS) | iZeroRunLength; + iVlcCodeLength = 18; + } + + code.set(iMdecCode); + return iVlcCodeLength; + + } else { + ZeroRunLengthAcTables.AcCode ac = ZeroRunLengthAcTables.lookupTable2(iNext32Bits & BOTTOM_14_BITS); + ac.setMdec(code); + return ac._sBits.length(); + } + } + + private static int readTable3(@Nonnull MdecCode code, int iNext32Bits) throws MdecException.ReadCorruption { + if ((iNext32Bits & BOTTOM_9_BITS) == 0) { + // escape code + + int iMdecCode; + int iVlcCodeLength; + if ((iNext32Bits & BIT10) != 0) { + // positive + iMdecCode = iNext32Bits >> 10; + iVlcCodeLength = 26; + } else { + // negative + int iSignExtendAc = (iNext32Bits << 18) >> 28; + int iZeroRunLength = (iNext32Bits >> 4) & 0x3c00; + iMdecCode = (iSignExtendAc & BOTTOM_10_BITS) | iZeroRunLength; + iVlcCodeLength = 18; + } + + code.set(iMdecCode); + return iVlcCodeLength; + + } else { + ZeroRunLengthAcTables.AcCode ac = ZeroRunLengthAcTables.lookupTable3(iNext32Bits & BOTTOM_14_BITS); + ac.setMdec(code); + return ac._sBits.length(); + } + } + + /** + * This bit reader is actually pretty clever. It works backwards from the + * normal bistream approach, which I think might make it faster. But then + * again, the mpeg1 spec chose its bitstream style for a reason. + */ + private static class AconcaguaBitReader { + + private int _iFrontBits; + private int _iMidBits; + private int _iBackBits; + private int _iBitsRemaining; + @Nonnull + private final byte[] _abBitstream; + private int _iPos = -4; // meh workaround so I can call resetColumn() in the constructor + + public AconcaguaBitReader(@Nonnull byte[] abBitstream, int iStartPos) { + _abBitstream = abBitstream; + resetColumn(); + } + + final public void resetColumn() { + _iBitsRemaining = 32; + _iPos = _iPos + 4; + _iFrontBits = IO.readSInt32LE(_abBitstream, _iPos); + _iMidBits = IO.readSInt32LE(_abBitstream, _iPos + 4); + _iBackBits = IO.readSInt32LE(_abBitstream, _iPos + 8); + } + + public int getBits() { + return _iFrontBits; + } + + public void skipBits(int iNumBits) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println(String.format("Skip %d bits %s", iNumBits, Misc.bitsToString(_iFrontBits, iNumBits))); + _iFrontBits = _iFrontBits >>> iNumBits; + _iBitsRemaining = _iBitsRemaining - iNumBits; + int i = _iMidBits << (32 - iNumBits); + _iMidBits = _iMidBits >>> iNumBits; + _iFrontBits = _iFrontBits | i; + + if (_iBitsRemaining < 0) + loadMoreBits(); + } + + public void loadMoreBits() { + _iMidBits = _iBackBits; + if (_iPos + 12 < _abBitstream.length) + _iBackBits = IO.readSInt32LE(_abBitstream, _iPos + 12); + else + _iBackBits = 0; + _iBitsRemaining = _iBitsRemaining + 32; + int rTemp = _iMidBits << _iBitsRemaining; + _iFrontBits = _iFrontBits | rTemp; + rTemp = 32 - _iBitsRemaining; + _iMidBits = _iMidBits >>> rTemp; + + _iPos+=4; + } + + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/DcTable.java b/jpsxdec/src/jpsxdec/modules/aconcagua/DcTable.java new file mode 100644 index 0000000..bec3706 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/DcTable.java @@ -0,0 +1,366 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecException; +import jpsxdec.util.Misc; + +/** Pretty standard variable length code bit reading mapped to DC codes. + * This is for the Aconcagua opening FMV. The ending FMV seems to be different. */ +public class DcTable { + + + public static class DcCode { + @Nonnull + private final String _sBits; + private final int _iRelativeDcCoefficient; + + protected DcCode(@Nonnull String sBits, int iRelativeDcCoefficient) { + _sBits = sBits; + _iRelativeDcCoefficient = iRelativeDcCoefficient; + } + + public int getBitCodeLen() { + return _sBits.length(); + } + + public int getRelativeDcCoefficient() { + return _iRelativeDcCoefficient; + } + + @Override + public String toString() { + return make(); + } + + public @Nonnull String make() { + return String.format("new DcCode(%13s, %5d),", + "\""+_sBits+"\"", _iRelativeDcCoefficient); + } + } + + public static @Nonnull DcCode lookup(int iBottom11Bits) throws MdecException.ReadCorruption { + DcCode code = LOOKUP[iBottom11Bits & BOTTOM_11_BITS]; + if (code == null) + throw new MdecException.ReadCorruption(Misc.bitsToString(iBottom11Bits, BIT_LENGTH)); + return code; + } + + private static final int BIT_LENGTH = 11; + private static final int ENTRY_COUNT = 2048; + private static final int BOTTOM_11_BITS = 0x7FF; + private static void buildLookup() { + for (DcCode dc : DC_CODES) { + int iBits = Integer.parseInt(dc._sBits, 2); + int iMax = BOTTOM_11_BITS >>> dc._sBits.length(); + + for (int i = 0; i <= iMax; i++) { + int j = (i << dc._sBits.length()) | iBits; + if (LOOKUP[j] != null) + throw new RuntimeException("Wrong logic"); + LOOKUP[j] = dc; + } + } + } + + static final DcCode[] LOOKUP = new DcCode[ENTRY_COUNT]; + + private static final DcCode[] DC_CODES = { + new DcCode( "100", -1024), + new DcCode( "0011", 1), + new DcCode( "1101", 1023), + new DcCode( "01110", -2), + new DcCode( "10111", -3), + new DcCode( "11110", -1022), + new DcCode( "11111", -1021), + new DcCode( "001001", 1020), + new DcCode( "100001", 4), + new DcCode( "110001", 516), + new DcCode( "111011", 5), + new DcCode( "0000010", -5), + new DcCode( "0000110", -6), + new DcCode( "0001010", -1018), + new DcCode( "0100110", -1017), + new DcCode( "1000101", -1016), + new DcCode( "1011001", -7), + new DcCode( "1011011", -1015), + new DcCode( "1110101", -8), + new DcCode( "00001111", 1004), + new DcCode( "00100010", 1015), + new DcCode( "00101010", 11), + new DcCode( "00101111", 1001), + new DcCode( "00111010", 12), + new DcCode( "01001010", 1011), + new DcCode( "01010001", 15), + new DcCode( "01011010", 1012), + new DcCode( "01100010", 10), + new DcCode( "01110010", 1014), + new DcCode( "01111010", 1010), + new DcCode( "10000001", 13), + new DcCode( "10000101", 1006), + new DcCode( "10001011", 1003), + new DcCode( "10001111", 20), + new DcCode( "10101011", 1005), + new DcCode( "10101111", 18), + new DcCode( "10110110", 1008), + new DcCode( "11000111", 19), + new DcCode( "11001011", 17), + new DcCode( "11010001", 1009), + new DcCode( "11010101", 14), + new DcCode( "11101010", 1013), + new DcCode( "11101011", 1007), + new DcCode( "11111001", 16), + new DcCode( "000000001", -34), + new DcCode( "000000101", -983), + new DcCode( "000010001", -36), + new DcCode( "000010010", -28), + new DcCode( "000010101", -55), + new DcCode( "000010110", -30), + new DcCode( "000100101", -984), + new DcCode( "000100111", -963), + new DcCode( "000101011", -51), + new DcCode( "000110110", -992), + new DcCode( "001001111", -960), + new DcCode( "001010010", -997), + new DcCode( "001010110", -985), + new DcCode( "001100101", -976), + new DcCode( "001100111", -965), + new DcCode( "001101001", -990), + new DcCode( "001111001", -41), + new DcCode( "010010101", -972), + new DcCode( "010011011", -57), + new DcCode( "010100010", -1003), + new DcCode( "010100111", -974), + new DcCode( "010101001", -38), + new DcCode( "010101010", -1001), + new DcCode( "010110010", -24), + new DcCode( "010111001", -977), + new DcCode( "011000110", -33), + new DcCode( "011001111", -52), + new DcCode( "011100010", -999), + new DcCode( "011100101", -44), + new DcCode( "011100111", -59), + new DcCode( "011101001", -979), + new DcCode( "011110010", -1000), + new DcCode( "011110110", -991), + new DcCode( "011111010", -37), + new DcCode( "100000001", -989), + new DcCode( "100000111", -53), + new DcCode( "100001011", -54), + new DcCode( "100010001", -35), + new DcCode( "100010010", -25), + new DcCode( "100010101", -46), + new DcCode( "100010110", -39), + new DcCode( "100101011", -45), + new DcCode( "100110010", -26), + new DcCode( "100110110", -31), + new DcCode( "100111001", -981), + new DcCode( "101000001", -988), + new DcCode( "101000111", -967), + new DcCode( "101001011", -47), + new DcCode( "101001111", -50), + new DcCode( "101010101", -986), + new DcCode( "101100101", -40), + new DcCode( "101100111", -962), + new DcCode( "101101010", -998), + new DcCode( "101101011", -975), + new DcCode( "101101111", -970), + new DcCode( "101111001", -980), + new DcCode( "110000111", -969), + new DcCode( "110010001", -982), + new DcCode( "110010101", -987), + new DcCode( "110010110", -995), + new DcCode( "110011001", -48), + new DcCode( "110011010", -29), + new DcCode( "110011011", -43), + new DcCode( "110100101", -978), + new DcCode( "110100111", -968), + new DcCode( "110101010", -1002), + new DcCode( "110110101", -42), + new DcCode( "111000010", -22), + new DcCode( "111000110", -993), + new DcCode( "111011010", -994), + new DcCode( "111100111", -49), + new DcCode( "111110010", -27), + new DcCode( "111110110", -32), + new DcCode( "111111010", -996), + new DcCode( "0000000111", 111), + new DcCode( "0000001011", 909), + new DcCode( "0000011001", 901), + new DcCode( "0000011010", 946), + new DcCode( "0000011011", 120), + new DcCode( "0000101001", 936), + new DcCode( "0000110010", 69), + new DcCode( "0000110101", 903), + new DcCode( "0000111001", 90), + new DcCode( "0001000001", 941), + new DcCode( "0001000110", 67), + new DcCode( "0001000111", 121), + new DcCode( "0001001011", 898), + new DcCode( "0001010101", 920), + new DcCode( "0001100110", 940), + new DcCode( "0001101010", 58), + new DcCode( "0001101011", 912), + new DcCode( "0001101111", 126), + new DcCode( "0001110110", 926), + new DcCode( "0010000111", 914), + new DcCode( "0010010001", 103), + new DcCode( "0010010010", 77), + new DcCode( "0010010110", 948), + new DcCode( "0010011001", 107), + new DcCode( "0010011010", 957), + new DcCode( "0010100101", 100), + new DcCode( "0010110101", 109), + new DcCode( "0010111010", 951), + new DcCode( "0011000001", 89), + new DcCode( "0011000010", 962), + new DcCode( "0011001010", 958), + new DcCode( "0011010010", 963), + new DcCode( "0011010110", 942), + new DcCode( "0011011010", 97), + new DcCode( "0011100110", 76), + new DcCode( "0011101111", 907), + new DcCode( "0100000101", 110), + new DcCode( "0100011001", 98), + new DcCode( "0100011010", 82), + new DcCode( "0100011011", 128), + new DcCode( "0100100101", 932), + new DcCode( "0100100111", 119), + new DcCode( "0100101001", 928), + new DcCode( "0100110101", 911), + new DcCode( "0101000010", 53), + new DcCode( "0101000110", 74), + new DcCode( "0101010010", 68), + new DcCode( "0101010110", 947), + new DcCode( "0101100110", 86), + new DcCode( "0101101001", 931), + new DcCode( "0101110110", 952), + new DcCode( "0110010010", 961), + new DcCode( "0110100010", 960), + new DcCode( "0110101001", 927), + new DcCode( "0110110010", 944), + new DcCode( "0110111001", 915), + new DcCode( "0110111010", 956), + new DcCode( "0111000001", 91), + new DcCode( "0111001010", 65), + new DcCode( "0111001111", 108), + new DcCode( "0111010010", 968), + new DcCode( "0111010110", 938), + new DcCode( "0111100010", 954), + new DcCode( "0111100101", 96), + new DcCode( "0111100110", 935), + new DcCode( "0111101001", 923), + new DcCode( "0111101111", 886), + new DcCode( "1000000111", 906), + new DcCode( "1000001011", 890), + new DcCode( "1000011001", 102), + new DcCode( "1000011010", 950), + new DcCode( "1000011011", 114), + new DcCode( "1000101001", 917), + new DcCode( "1000110010", 66), + new DcCode( "1000110101", 99), + new DcCode( "1000111001", 908), + new DcCode( "1001000001", 930), + new DcCode( "1001000010", 964), + new DcCode( "1001000110", 93), + new DcCode( "1001000111", 904), + new DcCode( "1001001011", 106), + new DcCode( "1001010101", 916), + new DcCode( "1001100110", 81), + new DcCode( "1001101010", 87), + new DcCode( "1001101011", 895), + new DcCode( "1001101111", 118), + new DcCode( "1001110110", 922), + new DcCode( "1010000111", 132), + new DcCode( "1010010001", 929), + new DcCode( "1010010010", 70), + new DcCode( "1010010110", 924), + new DcCode( "1010011001", 92), + new DcCode( "1010011010", 84), + new DcCode( "1010100101", 918), + new DcCode( "1010110101", 105), + new DcCode( "1010111010", 85), + new DcCode( "1011000001", 905), + new DcCode( "1011000010", 966), + new DcCode( "1011001010", 955), + new DcCode( "1011010010", 80), + new DcCode( "1011010110", 79), + new DcCode( "1011011010", 75), + new DcCode( "1011100110", 71), + new DcCode( "1011101111", 896), + new DcCode( "1100000101", 101), + new DcCode( "1100011001", 72), + new DcCode( "1100011010", 943), + new DcCode( "1100011011", 104), + new DcCode( "1100100101", 115), + new DcCode( "1100100111", 910), + new DcCode( "1100101001", 925), + new DcCode( "1100110101", 899), + new DcCode( "1101000110", 933), + new DcCode( "1101010010", 60), + new DcCode( "1101010110", 937), + new DcCode( "1101100110", 94), + new DcCode( "1101101001", 913), + new DcCode( "1101110110", 83), + new DcCode( "1110010010", 949), + new DcCode( "1110100010", 959), + new DcCode( "1110101001", 921), + new DcCode( "1110110010", 63), + new DcCode( "1110111001", 939), + new DcCode( "1110111010", 934), + new DcCode( "1111000001", 88), + new DcCode( "1111001010", 945), + new DcCode( "1111001111", 893), + new DcCode( "1111010010", 78), + new DcCode( "1111010110", 953), + new DcCode( "1111100010", 51), + new DcCode( "1111100101", 919), + new DcCode( "1111100110", 73), + new DcCode( "1111101001", 95), + new DcCode( "1111101111", 116), + new DcCode("00001000010", -156), + new DcCode("01101000010", -879), + new DcCode("10001000010", -912), + new DcCode("11101000010", -124), + }; + + static { + buildLookup(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/DemuxedAconcaguaFrame.java b/jpsxdec/src/jpsxdec/modules/aconcagua/DemuxedAconcaguaFrame.java new file mode 100644 index 0000000..4bcaffb --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/DemuxedAconcaguaFrame.java @@ -0,0 +1,60 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import java.util.List; +import javax.annotation.Nonnull; +import jpsxdec.modules.video.sectorbased.DemuxedFrameWithNumberAndDims; +import jpsxdec.psxvideo.mdec.Calc; + + +public class DemuxedAconcaguaFrame extends DemuxedFrameWithNumberAndDims { + + private final int _iQuantizationScale; + + public DemuxedAconcaguaFrame(int iWidth, int iHeight, int iHeaderFrameNumber, @Nonnull List sectors, int iQuantizationScale) { + super(iWidth, iHeight, iHeaderFrameNumber, sectors); + _iQuantizationScale = iQuantizationScale; + } + + @Override + public @Nonnull BitStreamUncompressor_Aconcagua getCustomFrameMdecStream() { + byte[] abFrameData = copyDemuxData(); + return new BitStreamUncompressor_Aconcagua(Calc.macroblockDim(getHeight()), _iQuantizationScale, abFrameData); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/InstructionTable.java b/jpsxdec/src/jpsxdec/modules/aconcagua/InstructionTable.java new file mode 100644 index 0000000..3c4ffa0 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/InstructionTable.java @@ -0,0 +1,200 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecException; +import jpsxdec.util.Misc; + +/** The "instruction" table, as I call it, indicates how many codes to read + * from each of the three zero-run length AC tables for the current block. + * This is for the Aconcagua opening FMV. The ending FMV seems to be different. + * What even.. */ +public class InstructionTable { + + public static class InstructionCode { + @Nonnull + private final String _sBits; + private final int _iTable1Count; + private final int _iTable2Count; + private final int _iTable3Count; + + protected InstructionCode(@Nonnull String sBits, int iTable1Count, int iTable2Count, int iTable3Count) { + _sBits = sBits; + _iTable1Count = iTable1Count; + _iTable2Count = iTable2Count; + _iTable3Count = iTable3Count; + } + + public int getTable1Count() { + return _iTable1Count; + } + + public int getTable2Count() { + return _iTable2Count; + } + + public int getTable3Count() { + return _iTable3Count; + } + + public int getBitCodeLen() { + return _sBits.length(); + } + + public String getBits() { + return _sBits; + } + + @Override + public String toString() { + return make(); + } + + public @Nonnull String make() { + return String.format("new InstructionCode(%12s, %2d, %2d, %2d),", + "\""+_sBits+"\"", _iTable1Count, _iTable2Count, _iTable3Count); + } + } + + private static final int BOTTOM_10_BITS = 0x03ff; + public static @Nonnull InstructionCode lookup(int iBottom10Bits) throws MdecException.ReadCorruption { + iBottom10Bits &= BOTTOM_10_BITS; + int iOnesCount; + int b = iBottom10Bits; + for (iOnesCount = 0; iOnesCount < 9; iOnesCount++) { + if ((b & 1) == 0) + break; + b >>>= 1; + } + + if (iOnesCount <= 3) { + return INSTRUCTIONS[iOnesCount]; + } else if (iOnesCount < 7) { + InstructionCode code = PARTIAL_LOOKUP[(iBottom10Bits >>> 4)]; + if (code == null) + throw new MdecException.ReadCorruption("Aconcagua lookup table bits " + Misc.bitsToString(iBottom10Bits, 10)); + return code; + } else if (iOnesCount <= 9) { + return INSTRUCTIONS[4 + ((iBottom10Bits >>> 7) & 3)]; + } + throw new MdecException.ReadCorruption("Aconcagua lookup table bits " + Misc.bitsToString(iBottom10Bits, 10)); + } + + private static void buildLookup() { + for (int i = 8; i < INSTRUCTIONS.length; i++) { + InstructionCode code = INSTRUCTIONS[i]; + int iLookupPos = Integer.parseInt(code._sBits, 2); + PARTIAL_LOOKUP[iLookupPos >>> 4] = code; + } + } + + private static final int _111110b = 0x3E; + private static final InstructionCode[] PARTIAL_LOOKUP = new InstructionCode[_111110b + 1]; + + private static final InstructionCode[] INSTRUCTIONS = { + new InstructionCode( "0", 0, 0, 0), + new InstructionCode( "01", 0, 0, 1), + new InstructionCode( "011", 0, 0, 2), + new InstructionCode( "0111", 0, 0, 3), + new InstructionCode( "001111111", 0, 0, 4), + new InstructionCode( "011111111", 3, 1, 12), + new InstructionCode( "101111111", 3, 1, 14), + new InstructionCode( "111111111", 3, 1, 13), + new InstructionCode("0000001111", 5, 2, 19), + new InstructionCode("0000011111", 7, 43, 8), + new InstructionCode("0000101111", 10, 7, 25), + new InstructionCode("0000111111", 2, 0, 10), + new InstructionCode("0001001111", 1, 0, 8), + new InstructionCode("0001011111", 6, 27, 17), + new InstructionCode("0001101111", 8, 4, 22), + new InstructionCode("0010001111", 6, 3, 21), + new InstructionCode("0010011111", 1, 56, 5), + new InstructionCode("0010101111", 8, 16, 22), + new InstructionCode("0010111111", 1, 1, 9), + new InstructionCode("0011001111", 0, 1, 5), + new InstructionCode("0011011111", 9, 24, 19), + new InstructionCode("0011101111", 9, 5, 24), + new InstructionCode("0100001111", 0, 0, 5), + new InstructionCode("0100011111", 7, 44, 8), + new InstructionCode("0100101111", 9, 6, 25), + new InstructionCode("0100111111", 3, 0, 12), + new InstructionCode("0101001111", 1, 0, 9), + new InstructionCode("0101011111", 6, 25, 17), + new InstructionCode("0101101111", 7, 3, 22), + new InstructionCode("0110001111", 6, 2, 20), + new InstructionCode("0110011111", 8, 33, 14), + new InstructionCode("0110101111", 10, 9, 25), + new InstructionCode("0110111111", 2, 1, 10), + new InstructionCode("0111001111", 5, 1, 17), + new InstructionCode("0111011111", 8, 28, 17), + new InstructionCode("0111101111", 8, 5, 23), + new InstructionCode("1000001111", 6, 1, 20), + new InstructionCode("1000011111", 10, 34, 12), + new InstructionCode("1000101111", 8, 11, 24), + new InstructionCode("1000111111", 4, 1, 14), + new InstructionCode("1001001111", 1, 0, 7), + new InstructionCode("1001011111", 11, 24, 16), + new InstructionCode("1001101111", 8, 4, 23), + new InstructionCode("1010001111", 7, 3, 21), + new InstructionCode("1010011111", 0, 58, 5), + new InstructionCode("1010101111", 8, 18, 21), + new InstructionCode("1010111111", 4, 1, 16), + new InstructionCode("1011001111", 5, 1, 18), + new InstructionCode("1011011111", 2, 53, 5), + new InstructionCode("1011101111", 9, 6, 24), + new InstructionCode("1100001111", 5, 2, 18), + new InstructionCode("1100011111", 1, 54, 6), + new InstructionCode("1100101111", 9, 7, 25), + new InstructionCode("1100111111", 2, 1, 11), + new InstructionCode("1101001111", 4, 1, 17), + new InstructionCode("1101011111", 10, 20, 19), + new InstructionCode("1101101111", 7, 4, 22), + new InstructionCode("1110001111", 6, 3, 20), + new InstructionCode("1110011111", 15, 29, 13), + new InstructionCode("1110101111", 9, 12, 24), + new InstructionCode("1110111111", 4, 1, 15), + new InstructionCode("1111001111", 1, 0, 6), + new InstructionCode("1111011111", 8, 33, 13), + new InstructionCode("1111101111", 8, 6, 23), + }; + + static { + buildLookup(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/SectorAconcaguaVideo.java b/jpsxdec/src/jpsxdec/modules/aconcagua/SectorAconcaguaVideo.java new file mode 100644 index 0000000..24be20a --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/SectorAconcaguaVideo.java @@ -0,0 +1,172 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.i18n.exception.LocalizedIncompatibleException; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.IdentifiedSector; +import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; +import jpsxdec.modules.video.sectorbased.IVideoSectorWithFrameNumber; +import jpsxdec.modules.video.sectorbased.SectorBasedFrameReplace; +import jpsxdec.util.DemuxedData; + +/** Video sector of the Aconcagua opening FMV. The ending FMV is different. */ +public class SectorAconcaguaVideo extends IdentifiedSector + implements DemuxedData.Piece, ISelfDemuxingVideoSector, + SectorBasedFrameReplace.IReplaceableVideoSector, + IVideoSectorWithFrameNumber +{ + + private final static long MAGIC_NUMBER_BE = 0x60010200; + + // Magic // 4 bytes @0 + private int _iChunkNumber; // 2 bytes @4 + private int _iChunksInFrame; // 2 bytes @6 + private int _iFrameNumber; // 4 bytes @8 + private int _iCodeCount; // 4 bytes @12 + private int _iWidth; // 2 bytes @16 + private int _iHeight; // 2 bytes @18 + private int _iQuantizationScale; // 4 bytes @20 + // Zeroes // 8 bytes @24 + + public SectorAconcaguaVideo(CdSector cdSector) { + super(cdSector); + if (isSuperInvalidElseReset()) return; + + long lngMagic = cdSector.readUInt32BE(0); + if (lngMagic != MAGIC_NUMBER_BE) + return; + + _iChunkNumber = cdSector.readSInt16LE(4); + if (_iChunkNumber < 0 || _iChunkNumber > 10) + return; + _iChunksInFrame = cdSector.readSInt16LE(6); + if (_iChunksInFrame <= 0 || _iChunksInFrame > 10) + return; + _iFrameNumber = cdSector.readSInt32LE(8); + if (_iFrameNumber < 0) + return; + _iCodeCount = cdSector.readSInt32LE(12); + if (_iCodeCount < 1) + return; + _iWidth = cdSector.readSInt16LE(16); + if (_iWidth < 16 || _iWidth > 400) + return; + _iHeight = cdSector.readSInt16LE(18); + if (_iHeight < 16 || _iHeight > 400) + return; + _iQuantizationScale = cdSector.readSInt32LE(20); + if (_iQuantizationScale < 1 || _iQuantizationScale > 64) + return; + for (int i = 24; i < 32; i++) { + if (cdSector.readUserDataByte(i) != 0) + return; + } + + setProbability(100); + } + + public String getTypeName() { + return "Aconcagua Video"; + } + + public int getWidth() { + return _iWidth; + } + + public int getHeight() { + return _iHeight; + } + + public int getHeaderFrameNumber() { + return _iFrameNumber; + } + + public int getChunksInFrame() { + return _iChunksInFrame; + } + + public int getChunkNumber() { + return _iChunkNumber; + } + + public int getQuantizationScale() { + return _iQuantizationScale; + } + + public int getVideoSectorHeaderSize() { + return 32; + } + + public int getDemuxPieceSize() { + return getCdSector().getCdUserDataSize() - getVideoSectorHeaderSize(); + } + + public byte getDemuxPieceByte(int i) { + return getCdSector().readUserDataByte(i); + } + + public void copyDemuxPieceData(@Nonnull byte[] abOut, int iOutPos) { + getCdSector().getCdUserDataCopy(getVideoSectorHeaderSize(), + abOut, iOutPos, getDemuxPieceSize()); + } + + public @Nonnull AconcaguaDemuxer createDemuxer(@Nonnull ILocalizedLogger log) { + return new AconcaguaDemuxer(this, log); + } + + @Override + public String toString() { + return String.format("%s %s frame:%d chunk:%d/%d %dx%d codes:%d qscale=%d", + getTypeName(), + super.cdToString(), + _iFrameNumber, + _iChunkNumber, + _iChunksInFrame, + _iWidth, + _iHeight, + _iCodeCount, + _iQuantizationScale); + } + + public void replaceVideoSectorHeader(byte[] abNewDemuxData, int iNewUsedSize, int iNewMdecCodeCount, byte[] abCurrentVidSectorHeader) throws LocalizedIncompatibleException { + throw new UnsupportedOperationException("Not gonna support replacing Aconcagua video"); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/aconcagua/ZeroRunLengthAcTables.java b/jpsxdec/src/jpsxdec/modules/aconcagua/ZeroRunLengthAcTables.java new file mode 100644 index 0000000..795bbb7 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/aconcagua/ZeroRunLengthAcTables.java @@ -0,0 +1,662 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.aconcagua; + +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecException; +import jpsxdec.util.Misc; + +/** Pretty standard variable length code bit reading mapped to MDEC codes. + * These are for the Aconcagua opening FMV. The ending FMV seems to be different. */ +public class ZeroRunLengthAcTables { + + public static class AcCode { + @Nonnull + public final String _sBits; + @Nonnull + public final MdecCode _code; + + protected AcCode(@Nonnull String sBits, @Nonnull MdecCode code) { + _sBits = sBits; + _code = code; + } + + public @Nonnull MdecCode getMdecCodeCopy() { + return _code.copy(); + } + + public void setMdec(@Nonnull MdecCode code) { + code.setFrom(_code); + } + + @Override + public String toString() { + return make(); + } + + public @Nonnull String make() { + return String.format("new AcCode(%16s, new MdecCode(%2d, %4d)),", + "\""+_sBits+"\"", _code.getTop6Bits(), _code.getBottom10Bits()); + } + + } + + public static @Nonnull AcCode lookupTable1(int iBottom14Bits) throws MdecException.ReadCorruption { + return lookup(TABLE1_LOOKUP, iBottom14Bits); + } + + public static @Nonnull AcCode lookupTable2(int iBottom14Bits) throws MdecException.ReadCorruption { + return lookup(TABLE2_LOOKUP, iBottom14Bits); + } + + public static @Nonnull AcCode lookupTable3(int iBottom14Bits) throws MdecException.ReadCorruption { + return lookup(TABLE3_LOOKUP, iBottom14Bits); + } + + private static @Nonnull AcCode lookup(@Nonnull AcCode[] aoTable, int iBottom14Bits) + throws MdecException.ReadCorruption + { + AcCode code = aoTable[iBottom14Bits & BOTTOM_14_BITS]; + if (code == null) + throw new MdecException.ReadCorruption(Misc.bitsToString(iBottom14Bits, BIT_LENGTH)); + return code; + } + + private static final int BIT_LENGTH = 14; + private static final int ENTRY_COUNT = 16384; + private static final int BOTTOM_14_BITS = 0x3FFF; + + static final AcCode[] TABLE1_LOOKUP = new AcCode[ENTRY_COUNT]; + static final AcCode[] TABLE2_LOOKUP = new AcCode[ENTRY_COUNT]; + static final AcCode[] TABLE3_LOOKUP = new AcCode[ENTRY_COUNT]; + + private static void buidLookup(@Nonnull AcCode[] table, @Nonnull AcCode[] lookup) { + for (AcCode ac : table) { + int iBits = Integer.parseInt(ac._sBits, 2); + int iMax = BOTTOM_14_BITS >>> ac._sBits.length(); + + for (int i = 0; i <= iMax; i++) { + int j = (i << ac._sBits.length()) | iBits; + if (j >= lookup.length) + System.out.println(""); + if (lookup[j] != null) + throw new RuntimeException("Wrong logic"); + lookup[j] = ac; + } + } + } + + private static final AcCode[] TABLE1 = { + new AcCode( "0001", new MdecCode( 0, 2)), + new AcCode( "0010", new MdecCode( 0, 3)), + new AcCode( "0011", new MdecCode( 0, 1)), + new AcCode( "1100", new MdecCode( 0, -3)), + new AcCode( "1101", new MdecCode( 0, -1)), + new AcCode( "1110", new MdecCode( 0, -2)), + new AcCode( "00101", new MdecCode( 0, -5)), + new AcCode( "00111", new MdecCode( 0, -4)), + new AcCode( "01000", new MdecCode( 0, -7)), + new AcCode( "01010", new MdecCode( 0, 6)), + new AcCode( "01111", new MdecCode( 0, 4)), + new AcCode( "10110", new MdecCode( 0, -6)), + new AcCode( "11000", new MdecCode( 0, 7)), + new AcCode( "11011", new MdecCode( 0, 5)), + new AcCode( "000110", new MdecCode( 0, -10)), + new AcCode( "010100", new MdecCode( 0, -11)), + new AcCode( "100110", new MdecCode( 0, 10)), + new AcCode( "101001", new MdecCode( 0, -9)), + new AcCode( "110000", new MdecCode( 0, 12)), + new AcCode( "110100", new MdecCode( 0, 11)), + new AcCode( "110101", new MdecCode( 0, 9)), + new AcCode( "110111", new MdecCode( 0, -8)), + new AcCode( "111111", new MdecCode( 0, 8)), + new AcCode( "0001011", new MdecCode( 0, -13)), + new AcCode( "0011010", new MdecCode( 0, 15)), + new AcCode( "0011111", new MdecCode( 0, -12)), + new AcCode( "0100000", new MdecCode( 0, 16)), + new AcCode( "0111001", new MdecCode( 0, -14)), + new AcCode( "1000100", new MdecCode( 0, -15)), + new AcCode( "1001001", new MdecCode( 0, 14)), + new AcCode( "1001011", new MdecCode( 0, 13)), + new AcCode( "1011010", new MdecCode( 1, 1)), + new AcCode( "1100100", new MdecCode( 1, -1)), + new AcCode( "00000100", new MdecCode( 0, 21)), + new AcCode( "00010000", new MdecCode( 0, 22)), + new AcCode( "00010111", new MdecCode( 1, -2)), + new AcCode( "00011001", new MdecCode( 0, 20)), + new AcCode( "00101011", new MdecCode( 0, -18)), + new AcCode( "01000000", new MdecCode( 1, 4)), + new AcCode( "01010111", new MdecCode( 0, 17)), + new AcCode( "01100000", new MdecCode( 0, -21)), + new AcCode( "01101011", new MdecCode( 1, 2)), + new AcCode( "01111001", new MdecCode( 1, 3)), + new AcCode( "10000000", new MdecCode( 0, -16)), + new AcCode( "10011001", new MdecCode( 0, 19)), + new AcCode( "11010000", new MdecCode( 1, -4)), + new AcCode( "11010101", new MdecCode( 0, 18)), + new AcCode( "11011001", new MdecCode( 0, -19)), + new AcCode( "11011111", new MdecCode( 0, -17)), + new AcCode( "11111001", new MdecCode( 1, -3)), + new AcCode( "11111010", new MdecCode( 0, -20)), + new AcCode( "001010101", new MdecCode( 0, 24)), + new AcCode( "001011111", new MdecCode( 0, -22)), + new AcCode( "001111010", new MdecCode( 0, -25)), + new AcCode( "010000100", new MdecCode( 1, -6)), + new AcCode( "010001001", new MdecCode( 0, 25)), + new AcCode( "010010111", new MdecCode( 0, -23)), + new AcCode( "010111010", new MdecCode( 0, -26)), + new AcCode( "100001001", new MdecCode( 0, -24)), + new AcCode( "100010101", new MdecCode( 1, -5)), + new AcCode( "100100100", new MdecCode( 0, -27)), + new AcCode( "101010000", new MdecCode( 0, -28)), + new AcCode( "101011111", new MdecCode( 0, 23)), + new AcCode( "110000100", new MdecCode( 0, 26)), + new AcCode( "110101011", new MdecCode( 1, 5)), + new AcCode( "110111010", new MdecCode( 1, 6)), + new AcCode( "111100000", new MdecCode( 0, 27)), + new AcCode( "0000001001", new MdecCode( 2, -1)), + new AcCode( "0000010101", new MdecCode( 0, 31)), + new AcCode( "0001011001", new MdecCode( 2, 1)), + new AcCode( "0010010000", new MdecCode( 1, 9)), + new AcCode( "0010010101", new MdecCode( 0, -31)), + new AcCode( "0010101011", new MdecCode( 1, 7)), + new AcCode( "0011000000", new MdecCode( 1, -9)), + new AcCode( "0101011001", new MdecCode( 0, -32)), + new AcCode( "0110010111", new MdecCode( 0, 28)), + new AcCode( "0110100100", new MdecCode( 0, -35)), + new AcCode( "0111000000", new MdecCode( 0, -36)), + new AcCode( "0111010111", new MdecCode( 0, -29)), + new AcCode( "1000010101", new MdecCode( 0, 30)), + new AcCode( "1000100100", new MdecCode( 0, 33)), + new AcCode( "1000111010", new MdecCode( 1, -8)), + new AcCode( "1010010000", new MdecCode( 0, 35)), + new AcCode( "1010010101", new MdecCode( 0, -30)), + new AcCode( "1010100100", new MdecCode( 0, -34)), + new AcCode( "1010101011", new MdecCode( 1, -7)), + new AcCode( "1100111010", new MdecCode( 0, 32)), + new AcCode( "1101111010", new MdecCode( 0, -33)), + new AcCode( "1110001001", new MdecCode( 1, 8)), + new AcCode( "1110010111", new MdecCode( 0, 29)), + new AcCode( "1111000000", new MdecCode( 0, -37)), + new AcCode( "00000111010", new MdecCode( 0, -42)), + new AcCode( "00010100100", new MdecCode( 0, -43)), + new AcCode( "00011010111", new MdecCode( 0, 34)), + new AcCode( "00011100000", new MdecCode( 0, 40)), + new AcCode( "00011101011", new MdecCode( 0, 39)), + new AcCode( "00100111010", new MdecCode( 1, -12)), + new AcCode( "00101010101", new MdecCode( 0, -45)), + new AcCode( "00101111010", new MdecCode( 1, -11)), + new AcCode( "00110001001", new MdecCode( 0, 44)), + new AcCode( "00110010000", new MdecCode( 0, 45)), + new AcCode( "00110010101", new MdecCode( 0, -44)), + new AcCode( "01000001001", new MdecCode( 0, -41)), + new AcCode( "01101010101", new MdecCode( 2, -2)), + new AcCode( "01110010000", new MdecCode( 0, 42)), + new AcCode( "01110010101", new MdecCode( 0, -39)), + new AcCode( "01111010111", new MdecCode( 0, 38)), + new AcCode( "01111101011", new MdecCode( 1, 10)), + new AcCode( "10001010000", new MdecCode( 1, 12)), + new AcCode( "10010100100", new MdecCode( 0, -48)), + new AcCode( "10011100000", new MdecCode( 1, 11)), + new AcCode( "10011101011", new MdecCode( 1, -10)), + new AcCode( "10110010000", new MdecCode( 0, -46)), + new AcCode( "10111101011", new MdecCode( 0, 37)), + new AcCode( "11001011001", new MdecCode( 0, 41)), + new AcCode( "11011010111", new MdecCode( 0, 36)), + new AcCode( "11011101011", new MdecCode( 0, -38)), + new AcCode( "11110010101", new MdecCode( 0, -40)), + new AcCode( "11110100100", new MdecCode( 2, 3)), + new AcCode( "11111101011", new MdecCode( 2, 2)), + new AcCode( "000000100100", new MdecCode( 0, 48)), + new AcCode( "000111101011", new MdecCode( 2, -3)), + new AcCode( "001001010000", new MdecCode( 0, 56)), + new AcCode( "001011000000", new MdecCode( 1, 15)), + new AcCode( "001011010111", new MdecCode( 0, -47)), + new AcCode( "001011100000", new MdecCode( 0, -57)), + new AcCode( "001011101011", new MdecCode( 1, 13)), + new AcCode( "010000111010", new MdecCode( 1, -14)), + new AcCode( "010011010111", new MdecCode( 0, 46)), + new AcCode( "010101111010", new MdecCode( 0, 43)), + new AcCode( "010110001001", new MdecCode( 1, 14)), + new AcCode( "010110010101", new MdecCode( 0, -53)), + new AcCode( "011000001001", new MdecCode( 2, 4)), + new AcCode( "011001010000", new MdecCode( 0, 52)), + new AcCode( "011011100000", new MdecCode( 0, 50)), + new AcCode( "011101011001", new MdecCode( 3, 1)), + new AcCode( "100000100100", new MdecCode( 1, -15)), + new AcCode( "100001010000", new MdecCode( 3, -1)), + new AcCode( "100111101011", new MdecCode( 1, -13)), + new AcCode( "101001010000", new MdecCode( 1, 16)), + new AcCode( "101001011001", new MdecCode( 2, -4)), + new AcCode( "101011000000", new MdecCode( 0, 49)), + new AcCode( "101011010111", new MdecCode( 0, 47)), + new AcCode( "101011100000", new MdecCode( 0, 51)), + new AcCode( "110000100100", new MdecCode( 0, -56)), + new AcCode( "110101111010", new MdecCode( 0, -49)), + new AcCode( "110110001001", new MdecCode( 0, -52)), + new AcCode( "111011000000", new MdecCode( 1, -16)), + new AcCode( "111101011001", new MdecCode( 0, -50)), + new AcCode( "111111010111", new MdecCode( 0, -51)), + new AcCode( "0001101011001", new MdecCode( 0, 57)), + new AcCode( "0010000100100", new MdecCode( 0, 60)), + new AcCode( "0011011000000", new MdecCode( 1, -21)), + new AcCode( "0011101010101", new MdecCode( 0, 53)), + new AcCode( "0011110010000", new MdecCode( 1, 20)), + new AcCode( "0011111010111", new MdecCode( 0, -60)), + new AcCode( "0101101011001", new MdecCode( 1, 17)), + new AcCode( "0110000111010", new MdecCode( 0, -59)), + new AcCode( "0110011010111", new MdecCode( 2, 5)), + new AcCode( "0110100111010", new MdecCode( 3, -2)), + new AcCode( "0111000001001", new MdecCode( 0, 61)), + new AcCode( "0111011100000", new MdecCode( 1, -18)), + new AcCode( "0111101010101", new MdecCode( 0, 55)), + new AcCode( "0111110010000", new MdecCode( 1, 19)), + new AcCode( "1000001010000", new MdecCode( 3, 2)), + new AcCode( "1001110100100", new MdecCode( 0, 59)), + new AcCode( "1010100111010", new MdecCode( 0, 54)), + new AcCode( "1010101010101", new MdecCode( 0, -54)), + new AcCode( "1011011000000", new MdecCode( 0, 67)), + new AcCode( "1011110010000", new MdecCode( 0, 82)), + new AcCode( "1101011101011", new MdecCode( 0, -66)), + new AcCode( "1101101011001", new MdecCode( 0, -72)), + new AcCode( "1110000111010", new MdecCode( 2, 6)), + new AcCode( "1110100111010", new MdecCode( 2, -5)), + new AcCode( "1111000001001", new MdecCode( 1, -17)), + new AcCode( "1111001010000", new MdecCode( 0, -70)), + new AcCode( "1111101010101", new MdecCode( 0, -55)), + new AcCode( "1111110010000", new MdecCode( 1, 18)), + new AcCode("00000001010000", new MdecCode( 0, 69)), + new AcCode("00001001011001", new MdecCode( 1, 21)), + new AcCode("00001110100100", new MdecCode( 1, 27)), + new AcCode("00010100111010", new MdecCode( 3, 3)), + new AcCode("00010101010101", new MdecCode( 0, 66)), + new AcCode("00101011101011", new MdecCode( 0, 80)), + new AcCode("00101110100100", new MdecCode( 0, 63)), + new AcCode("00110101010101", new MdecCode( 0, 58)), + new AcCode("00110110010101", new MdecCode( 1, -20)), + new AcCode("00111001010000", new MdecCode( 1, -26)), + new AcCode("01001001011001", new MdecCode( 0, 109)), + new AcCode("01001101011001", new MdecCode( 0, -170)), + new AcCode("01010000100100", new MdecCode( 0, -68)), + new AcCode("01011101010101", new MdecCode( 1, 26)), + new AcCode("01011111010111", new MdecCode( 0, -58)), + new AcCode("01101110100100", new MdecCode( 0, 68)), + new AcCode("01110011010111", new MdecCode( 0, -69)), + new AcCode("01110101010101", new MdecCode( 2, 7)), + new AcCode("01110110010101", new MdecCode( 0, -61)), + new AcCode("01111011100000", new MdecCode( 0, 64)), + new AcCode("10000001010000", new MdecCode( 0, -85)), + new AcCode("10001001011001", new MdecCode( 0, -63)), + new AcCode("10001110100100", new MdecCode( 1, 22)), + new AcCode("10010100111010", new MdecCode( 0, -71)), + new AcCode("10010101010101", new MdecCode( 0, -67)), + new AcCode("10101011101011", new MdecCode( 0, 65)), + new AcCode("10101110100100", new MdecCode( 2, -7)), + new AcCode("10110101010101", new MdecCode( 1, -19)), + new AcCode("10110110010101", new MdecCode( 2, -6)), + new AcCode("10111001010000", new MdecCode( 0, 72)), + new AcCode("11001001011001", new MdecCode( 0, -122)), + new AcCode("11001101011001", new MdecCode( 3, -3)), + new AcCode("11010000100100", new MdecCode( 0, -86)), + new AcCode("11011101010101", new MdecCode( 0, 62)), + new AcCode("11011111010111", new MdecCode( 0, -62)), + new AcCode("11101110100100", new MdecCode( 0, -64)), + new AcCode("11110011010111", new MdecCode( 0, -94)), + new AcCode("11110101010101", new MdecCode( 0, -109)), + new AcCode("11110110010101", new MdecCode( 4, -1)), + new AcCode("11111011100000", new MdecCode( 0, -75)), + }; + + private static final AcCode[] TABLE2 = { + new AcCode( "001", new MdecCode( 0, 1)), + new AcCode( "110", new MdecCode( 0, -1)), + new AcCode( "0100", new MdecCode( 0, 4)), + new AcCode( "0101", new MdecCode( 0, 3)), + new AcCode( "0111", new MdecCode( 0, -2)), + new AcCode( "1000", new MdecCode( 0, -4)), + new AcCode( "1010", new MdecCode( 0, -3)), + new AcCode( "1111", new MdecCode( 0, 2)), + new AcCode( "01101", new MdecCode( 0, -5)), + new AcCode( "10000", new MdecCode( 0, -6)), + new AcCode( "11100", new MdecCode( 0, 6)), + new AcCode( "11101", new MdecCode( 0, 5)), + new AcCode( "000011", new MdecCode( 0, -7)), + new AcCode( "001011", new MdecCode( 1, -1)), + new AcCode( "100010", new MdecCode( 0, -8)), + new AcCode( "100011", new MdecCode( 1, 1)), + new AcCode( "101100", new MdecCode( 0, 8)), + new AcCode( "110011", new MdecCode( 0, 7)), + new AcCode( "0010010", new MdecCode( 0, -10)), + new AcCode( "0101011", new MdecCode( 0, -9)), + new AcCode( "0110010", new MdecCode( 1, 3)), + new AcCode( "0111011", new MdecCode( 1, -2)), + new AcCode( "1001100", new MdecCode( 0, 10)), + new AcCode( "1010010", new MdecCode( 1, -3)), + new AcCode( "1011011", new MdecCode( 0, 9)), + new AcCode( "1111011", new MdecCode( 1, 2)), + new AcCode( "00001100", new MdecCode( 0, -14)), + new AcCode( "00010011", new MdecCode( 0, -12)), + new AcCode( "00100000", new MdecCode( 2, -1)), + new AcCode( "01000000", new MdecCode( 0, 14)), + new AcCode( "01010011", new MdecCode( 1, -4)), + new AcCode( "01101011", new MdecCode( 1, 4)), + new AcCode( "10001100", new MdecCode( 0, 15)), + new AcCode( "10011011", new MdecCode( 0, 11)), + new AcCode( "10100000", new MdecCode( 2, 1)), + new AcCode( "11000010", new MdecCode( 0, 13)), + new AcCode( "11010011", new MdecCode( 0, -11)), + new AcCode( "11100000", new MdecCode( 0, -13)), + new AcCode( "11101011", new MdecCode( 0, 12)), + new AcCode( "000000010", new MdecCode( 0, 18)), + new AcCode( "000011011", new MdecCode( 1, 5)), + new AcCode( "001000010", new MdecCode( 2, -2)), + new AcCode( "001100000", new MdecCode( 0, 16)), + new AcCode( "001110010", new MdecCode( 2, 2)), + new AcCode( "010000000", new MdecCode( 1, -7)), + new AcCode( "010000010", new MdecCode( 0, -17)), + new AcCode( "010010011", new MdecCode( 0, -15)), + new AcCode( "100000000", new MdecCode( 1, -5)), + new AcCode( "100000010", new MdecCode( 0, -16)), + new AcCode( "101000010", new MdecCode( 0, 17)), + new AcCode( "101100000", new MdecCode( 0, 20)), + new AcCode( "101110010", new MdecCode( 1, 6)), + new AcCode( "111110010", new MdecCode( 1, -6)), + new AcCode( "0011110010", new MdecCode( 0, -20)), + new AcCode( "0100011011", new MdecCode( 0, -18)), + new AcCode( "0110000010", new MdecCode( 0, 23)), + new AcCode( "0110010011", new MdecCode( 1, 7)), + new AcCode( "1011110010", new MdecCode( 2, -3)), + new AcCode( "1100011011", new MdecCode( 0, 19)), + new AcCode( "1110010011", new MdecCode( 2, 3)), + new AcCode( "0111110000000", new MdecCode( 0, -19)), + new AcCode( "1111110000000", new MdecCode( 1, 8)), + new AcCode("00000011000000", new MdecCode( 0, -34)), + new AcCode("00000110000000", new MdecCode( 0, 42)), + new AcCode("00000111000000", new MdecCode( 0, -39)), + new AcCode("00001011000000", new MdecCode( 2, -7)), + new AcCode("00001110000000", new MdecCode( 0, -23)), + new AcCode("00001110000010", new MdecCode( 3, 2)), + new AcCode("00001111000000", new MdecCode( 0, 37)), + new AcCode("00010011000000", new MdecCode( 1, -12)), + new AcCode("00010110000000", new MdecCode( 0, -38)), + new AcCode("00010111000000", new MdecCode( 0, -24)), + new AcCode("00011011000000", new MdecCode( 0, -41)), + new AcCode("00011110000000", new MdecCode( 0, -76)), + new AcCode("00011110000010", new MdecCode( 0, 22)), + new AcCode("00011111000000", new MdecCode( 0, -29)), + new AcCode("00100011000000", new MdecCode( 1, 12)), + new AcCode("00100110000000", new MdecCode( 0, -68)), + new AcCode("00100111000000", new MdecCode( 3, -4)), + new AcCode("00101011000000", new MdecCode( 0, -92)), + new AcCode("00101110000000", new MdecCode( 3, 1)), + new AcCode("00101110000010", new MdecCode( 0, 27)), + new AcCode("00101111000000", new MdecCode( 0, -35)), + new AcCode("00110011000000", new MdecCode( 0, -40)), + new AcCode("00110110000000", new MdecCode( 1, -20)), + new AcCode("00110111000000", new MdecCode( 0, -26)), + new AcCode("00111011000000", new MdecCode( 0, 59)), + new AcCode("00111110000010", new MdecCode( 1, 9)), + new AcCode("00111111000000", new MdecCode( 0, 40)), + new AcCode("01000011000000", new MdecCode( 0, 55)), + new AcCode("01000110000000", new MdecCode( 2, -13)), + new AcCode("01000111000000", new MdecCode( 0, -32)), + new AcCode("01001011000000", new MdecCode( 1, 18)), + new AcCode("01001110000000", new MdecCode( 0, 21)), + new AcCode("01001110000010", new MdecCode( 2, 4)), + new AcCode("01001111000000", new MdecCode( 0, 30)), + new AcCode("01010011000000", new MdecCode( 1, -15)), + new AcCode("01010110000000", new MdecCode( 0, 43)), + new AcCode("01010111000000", new MdecCode( 0, 32)), + new AcCode("01011011000000", new MdecCode( 3, 4)), + new AcCode("01011110000000", new MdecCode( 0, 56)), + new AcCode("01011110000010", new MdecCode( 0, -25)), + new AcCode("01011111000000", new MdecCode( 3, -3)), + new AcCode("01100011000000", new MdecCode( 2, 7)), + new AcCode("01100110000000", new MdecCode( 0, 53)), + new AcCode("01100111000000", new MdecCode( 0, -33)), + new AcCode("01101011000000", new MdecCode( 1, 15)), + new AcCode("01101110000000", new MdecCode( 1, -8)), + new AcCode("01101110000010", new MdecCode( 0, 29)), + new AcCode("01101111000000", new MdecCode( 4, -1)), + new AcCode("01110011000000", new MdecCode( 2, -6)), + new AcCode("01110110000000", new MdecCode( 1, -13)), + new AcCode("01110111000000", new MdecCode( 0, 39)), + new AcCode("01111011000000", new MdecCode( 0, -43)), + new AcCode("01111110000010", new MdecCode( 0, -27)), + new AcCode("01111111000000", new MdecCode( 2, -5)), + new AcCode("10000011000000", new MdecCode( 1, -11)), + new AcCode("10000110000000", new MdecCode( 0, 58)), + new AcCode("10000111000000", new MdecCode( 0, 31)), + new AcCode("10001011000000", new MdecCode( 3, 5)), + new AcCode("10001110000000", new MdecCode( 0, -21)), + new AcCode("10001110000010", new MdecCode( 3, -2)), + new AcCode("10001111000000", new MdecCode( 0, 44)), + new AcCode("10010011000000", new MdecCode( 0, -37)), + new AcCode("10010110000000", new MdecCode( 2, 9)), + new AcCode("10010111000000", new MdecCode( 0, 41)), + new AcCode("10011011000000", new MdecCode( 0, -49)), + new AcCode("10011110000000", new MdecCode( 0, -114)), + new AcCode("10011110000010", new MdecCode( 2, -4)), + new AcCode("10011111000000", new MdecCode( 0, 36)), + new AcCode("10100011000000", new MdecCode( 4, 3)), + new AcCode("10100110000000", new MdecCode( 0, -71)), + new AcCode("10100111000000", new MdecCode( 3, 3)), + new AcCode("10101011000000", new MdecCode( 0, 50)), + new AcCode("10101110000000", new MdecCode( 0, -22)), + new AcCode("10101110000010", new MdecCode( 0, -28)), + new AcCode("10101111000000", new MdecCode( 1, -14)), + new AcCode("10110011000000", new MdecCode( 0, -44)), + new AcCode("10110110000000", new MdecCode( 2, -8)), + new AcCode("10110111000000", new MdecCode( 0, -30)), + new AcCode("10111011000000", new MdecCode( 1, -18)), + new AcCode("10111110000010", new MdecCode( 0, 24)), + new AcCode("10111111000000", new MdecCode( 1, 11)), + new AcCode("11000011000000", new MdecCode( 0, 57)), + new AcCode("11000110000000", new MdecCode( 0, 47)), + new AcCode("11000111000000", new MdecCode( 1, 10)), + new AcCode("11001011000000", new MdecCode( 4, 2)), + new AcCode("11001110000000", new MdecCode( 3, -1)), + new AcCode("11001110000010", new MdecCode( 0, 26)), + new AcCode("11001111000000", new MdecCode( 2, 6)), + new AcCode("11010011000000", new MdecCode( 0, 34)), + new AcCode("11010110000000", new MdecCode( 4, -2)), + new AcCode("11010111000000", new MdecCode( 1, -9)), + new AcCode("11011011000000", new MdecCode( 1, 14)), + new AcCode("11011110000000", new MdecCode( 0, -57)), + new AcCode("11011110000010", new MdecCode( 0, 25)), + new AcCode("11011111000000", new MdecCode( 4, 1)), + new AcCode("11100011000000", new MdecCode( 0, -42)), + new AcCode("11100110000000", new MdecCode( 0, -45)), + new AcCode("11100111000000", new MdecCode( 0, -36)), + new AcCode("11101011000000", new MdecCode( 2, 8)), + new AcCode("11101110000000", new MdecCode( 0, 33)), + new AcCode("11101110000010", new MdecCode( 0, -31)), + new AcCode("11101111000000", new MdecCode( 0, 35)), + new AcCode("11110011000000", new MdecCode( 0, 38)), + new AcCode("11110110000000", new MdecCode( 1, 13)), + new AcCode("11110111000000", new MdecCode( 2, 5)), + new AcCode("11111011000000", new MdecCode( 0, -56)), + new AcCode("11111110000010", new MdecCode( 0, 28)), + new AcCode("11111111000000", new MdecCode( 1, -10)), + }; + + private static final AcCode[] TABLE3 = { + new AcCode( "10", new MdecCode( 0, 1)), + new AcCode( "101", new MdecCode( 0, -1)), + new AcCode( "0001", new MdecCode( 1, -1)), + new AcCode( "0011", new MdecCode( 0, -2)), + new AcCode( "1011", new MdecCode( 0, 2)), + new AcCode( "1111", new MdecCode( 1, 1)), + new AcCode( "00100", new MdecCode( 0, 3)), + new AcCode( "01100", new MdecCode( 2, 1)), + new AcCode( "10100", new MdecCode( 0, -3)), + new AcCode( "11100", new MdecCode( 2, -1)), + new AcCode( "001000", new MdecCode( 1, 2)), + new AcCode( "010000", new MdecCode( 4, -1)), + new AcCode( "011001", new MdecCode( 3, 1)), + new AcCode( "100000", new MdecCode( 0, -4)), + new AcCode( "100111", new MdecCode( 3, -1)), + new AcCode( "101000", new MdecCode( 0, 4)), + new AcCode( "111000", new MdecCode( 1, -2)), + new AcCode( "0011000", new MdecCode( 0, -5)), + new AcCode( "0101001", new MdecCode( 4, 1)), + new AcCode( "0110000", new MdecCode( 6, 1)), + new AcCode( "1000000", new MdecCode( 6, -1)), + new AcCode( "1001001", new MdecCode( 5, -1)), + new AcCode( "1010111", new MdecCode( 0, 5)), + new AcCode( "1111001", new MdecCode( 5, 1)), + new AcCode( "00000111", new MdecCode( 1, -3)), + new AcCode( "00001001", new MdecCode( 7, -1)), + new AcCode( "01000111", new MdecCode( 0, 6)), + new AcCode( "01011000", new MdecCode( 8, -1)), + new AcCode( "01110111", new MdecCode( 0, -6)), + new AcCode( "10000111", new MdecCode( 1, 3)), + new AcCode( "10110111", new MdecCode( 2, -2)), + new AcCode( "10111001", new MdecCode( 7, 1)), + new AcCode( "11110111", new MdecCode( 2, 2)), + new AcCode( "001101001", new MdecCode( 9, 1)), + new AcCode( "010010111", new MdecCode( 3, 2)), + new AcCode( "011011000", new MdecCode( 3, -2)), + new AcCode( "011110000", new MdecCode( 1, -4)), + new AcCode( "100000000", new MdecCode( 8, 1)), + new AcCode( "100110111", new MdecCode(10, 1)), + new AcCode( "100111001", new MdecCode( 0, -7)), + new AcCode( "101101001", new MdecCode( 9, -1)), + new AcCode( "110000000", new MdecCode(11, 1)), + new AcCode( "110001001", new MdecCode( 0, 7)), + new AcCode( "111000111", new MdecCode(10, -1)), + new AcCode( "111011000", new MdecCode( 1, 4)), + new AcCode( "111110000", new MdecCode( 0, 8)), + new AcCode( "0000010111", new MdecCode( 0, -9)), + new AcCode( "0000110111", new MdecCode( 2, -3)), + new AcCode( "0000111001", new MdecCode(12, -1)), + new AcCode( "0011000111", new MdecCode(12, 1)), + new AcCode( "0011101001", new MdecCode(11, -1)), + new AcCode( "0110010111", new MdecCode( 4, 2)), + new AcCode( "1000010111", new MdecCode(13, 1)), + new AcCode( "1000110111", new MdecCode( 0, 9)), + new AcCode( "1010000000", new MdecCode( 1, 5)), + new AcCode( "1011000111", new MdecCode( 2, 3)), + new AcCode( "1011101001", new MdecCode( 0, -8)), + new AcCode( "1100010111", new MdecCode(13, -1)), + new AcCode( "1110010111", new MdecCode( 4, -2)), + new AcCode( "00010000000", new MdecCode( 0, -11)), + new AcCode( "00010001001", new MdecCode( 5, -2)), + new AcCode( "00100010111", new MdecCode(14, -1)), + new AcCode( "00111101001", new MdecCode( 1, -5)), + new AcCode( "01000111001", new MdecCode( 5, 2)), + new AcCode( "01010001001", new MdecCode( 0, -10)), + new AcCode( "10010000000", new MdecCode(14, 1)), + new AcCode( "10010001001", new MdecCode( 0, 10)), + new AcCode( "10100010111", new MdecCode( 0, 11)), + new AcCode( "11101110000", new MdecCode( 2, -4)), + new AcCode( "001111101001", new MdecCode( 3, -3)), + new AcCode( "010111101001", new MdecCode( 3, 3)), + new AcCode( "011000111001", new MdecCode( 7, 2)), + new AcCode( "011010001001", new MdecCode( 2, 4)), + new AcCode( "011111101001", new MdecCode( 6, 2)), + new AcCode( "101111101001", new MdecCode( 1, -6)), + new AcCode( "110111101001", new MdecCode( 1, 6)), + new AcCode( "111000111001", new MdecCode( 0, -12)), + new AcCode( "111010001001", new MdecCode( 0, 12)), + new AcCode( "111111101001", new MdecCode( 6, -2)), + new AcCode("00000001110000", new MdecCode( 2, 5)), + new AcCode("00000101110000", new MdecCode(19, -1)), + new AcCode("00001001110000", new MdecCode(11, -2)), + new AcCode("00001101110000", new MdecCode( 0, -13)), + new AcCode("00010001110000", new MdecCode( 0, -14)), + new AcCode("00010101110000", new MdecCode(21, -1)), + new AcCode("00011001110000", new MdecCode( 1, 8)), + new AcCode("00100001110000", new MdecCode(17, -1)), + new AcCode("00100101110000", new MdecCode( 1, 9)), + new AcCode("00101001110000", new MdecCode( 3, -5)), + new AcCode("00101101110000", new MdecCode(15, 1)), + new AcCode("00110001110000", new MdecCode( 3, 4)), + new AcCode("00110101110000", new MdecCode( 6, 3)), + new AcCode("00111001110000", new MdecCode(17, 1)), + new AcCode("01000001110000", new MdecCode(16, -1)), + new AcCode("01000101110000", new MdecCode( 4, 4)), + new AcCode("01001001110000", new MdecCode( 9, -2)), + new AcCode("01001101110000", new MdecCode( 7, -2)), + new AcCode("01010001110000", new MdecCode( 4, -3)), + new AcCode("01010101110000", new MdecCode( 2, 6)), + new AcCode("01011001110000", new MdecCode(16, 1)), + new AcCode("01100001110000", new MdecCode( 5, -3)), + new AcCode("01100101110000", new MdecCode( 4, -4)), + new AcCode("01101001110000", new MdecCode(10, 2)), + new AcCode("01101101110000", new MdecCode(15, -1)), + new AcCode("01110001110000", new MdecCode( 0, 14)), + new AcCode("01110101110000", new MdecCode( 0, -17)), + new AcCode("01111001110000", new MdecCode( 0, 16)), + new AcCode("10000001110000", new MdecCode(11, 2)), + new AcCode("10000101110000", new MdecCode(20, -1)), + new AcCode("10001001110000", new MdecCode( 0, 17)), + new AcCode("10001101110000", new MdecCode( 1, 7)), + new AcCode("10010001110000", new MdecCode( 8, -2)), + new AcCode("10010101110000", new MdecCode(20, 1)), + new AcCode("10011001110000", new MdecCode( 1, -8)), + new AcCode("10100001110000", new MdecCode( 5, 3)), + new AcCode("10100101110000", new MdecCode( 7, 3)), + new AcCode("10101001110000", new MdecCode(10, -2)), + new AcCode("10101101110000", new MdecCode( 8, 2)), + new AcCode("10110001110000", new MdecCode( 3, -4)), + new AcCode("10110101110000", new MdecCode( 0, 18)), + new AcCode("10111001110000", new MdecCode(19, 1)), + new AcCode("11000001110000", new MdecCode( 0, -15)), + new AcCode("11000101110000", new MdecCode( 6, -3)), + new AcCode("11001001110000", new MdecCode(18, 1)), + new AcCode("11001101110000", new MdecCode( 0, 13)), + new AcCode("11010001110000", new MdecCode( 4, 3)), + new AcCode("11010101110000", new MdecCode(18, -1)), + new AcCode("11011001110000", new MdecCode( 0, -16)), + new AcCode("11100001110000", new MdecCode( 0, 15)), + new AcCode("11100101110000", new MdecCode( 0, -18)), + new AcCode("11101001110000", new MdecCode( 2, -6)), + new AcCode("11101101110000", new MdecCode( 1, -7)), + new AcCode("11110001110000", new MdecCode( 2, -5)), + new AcCode("11110101110000", new MdecCode(12, -2)), + new AcCode("11111001110000", new MdecCode( 9, 2)), + }; + + static { + buidLookup(TABLE1, TABLE1_LOOKUP); + buidLookup(TABLE2, TABLE2_LOOKUP); + buidLookup(TABLE3, TABLE3_LOOKUP); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketHeaderReader.java b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketHeaderReader.java index 4ffdce3..5f0d6c6 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketHeaderReader.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketHeaderReader.java @@ -86,7 +86,7 @@ public VideoHeader(@Nonnull byte[] abHeader, int iRemainingPayloadSize) private static final long AUDIO_ID = 0x08000200L; public static class AudioHeader implements Header { - private final int _iPresentationSample; + private final int _iPresentationSampleFrame; private final int _iByteSize; public AudioHeader(@Nonnull byte[] abHeader, int iRemainingPayloadSize) @@ -97,8 +97,8 @@ public AudioHeader(@Nonnull byte[] abHeader, int iRemainingPayloadSize) // always be sure the audio data is a multiple of 16*2 if (iRemainingPayloadSize % (SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT * 2) != 0) throw new BinaryDataNotRecognized(); - _iPresentationSample = IO.readSInt32BE(abHeader, 8); - if (_iPresentationSample < 0) + _iPresentationSampleFrame = IO.readSInt32BE(abHeader, 8); + if (_iPresentationSampleFrame < 0) throw new BinaryDataNotRecognized(); final long lngAudioId = IO.readUInt32BE(abHeader, 12); if (lngAudioId != AUDIO_ID) @@ -106,7 +106,7 @@ public AudioHeader(@Nonnull byte[] abHeader, int iRemainingPayloadSize) _iByteSize = iRemainingPayloadSize; } - public int getPresentationSample() { return _iPresentationSample; } + public int getPresentationSampleFrame() { return _iPresentationSampleFrame; } /** Guaranteed to be a multiple of 16*2 (i.e. stereo SPU sound units). */ public int getByteSize() { return _iByteSize; } } diff --git a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketToFrameAndAudio.java b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketToFrameAndAudio.java index 16711fb..0fadd40 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketToFrameAndAudio.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderPacketToFrameAndAudio.java @@ -47,6 +47,7 @@ import jpsxdec.adpcm.SpuAdpcmDecoder; import jpsxdec.adpcm.SpuAdpcmSoundUnit; import jpsxdec.i18n.I; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.sharedaudio.DecodedAudioPacket; import jpsxdec.util.DemuxedData; @@ -60,17 +61,19 @@ public class CrusaderPacketToFrameAndAudio implements CrusaderSectorToCrusaderPa /** Equivalent to 15 fps. */ public static final int SECTORS_PER_FRAME = 10; - public static final int CRUSADER_SAMPLES_PER_SECOND = 22050; - public static final int SAMPLES_PER_SECTOR = CRUSADER_SAMPLES_PER_SECOND / 150; + public static final int CRUSADER_SAMPLE_FRAMES_PER_SECOND = 22050; + public static final int SAMPLE_FRAMES_PER_SECTOR = CRUSADER_SAMPLE_FRAMES_PER_SECOND / 150; static { - if (CRUSADER_SAMPLES_PER_SECOND % 150 != 0) + if (CRUSADER_SAMPLE_FRAMES_PER_SECOND % 150 != 0) throw new RuntimeException("Crusader sample rate doesn't cleanly divide by sector rate"); } - public static final AudioFormat CRUSADER_AUDIO_FORMAT = new AudioFormat(CRUSADER_SAMPLES_PER_SECOND, 16, 2, true, false); + public static final AudioFormat CRUSADER_AUDIO_FORMAT = new AudioFormat(CRUSADER_SAMPLE_FRAMES_PER_SECOND, 16, 2, true, false); public interface FrameListener { - void frameComplete(@Nonnull DemuxedCrusaderFrame frame, @Nonnull ILocalizedLogger log); - void videoEnd(@Nonnull ILocalizedLogger log, int iStartSector, int iEndSector); + void frameComplete(@Nonnull DemuxedCrusaderFrame frame, @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void videoEnd(@Nonnull ILocalizedLogger log, int iStartSector, int iEndSector) + throws LoggedFailure; } @Nonnull @@ -120,6 +123,7 @@ public void setAudioListener(@CheckForNull DecodedAudioPacket.Listener audioList public void frame(@Nonnull CrusaderPacketHeaderReader.VideoHeader frameHeader, @Nonnull DemuxedData demux, @Nonnull ILocalizedLogger log) + throws LoggedFailure { int iPresentationSector = frameHeader.getFrameNumber() * SECTORS_PER_FRAME + _iAbsoluteInitialFramePresentationSector; @@ -134,6 +138,7 @@ public void frame(@Nonnull CrusaderPacketHeaderReader.VideoHeader frameHeader, public void audio(@Nonnull CrusaderPacketHeaderReader.AudioHeader audio, @Nonnull DemuxedData demux, @Nonnull ILocalizedLogger log) + throws LoggedFailure { // .. copy the audio data out of the sectors ............... byte[] abAudioDemuxBuffer = demux.copyDemuxData(); @@ -162,11 +167,12 @@ public void audio(@Nonnull CrusaderPacketHeaderReader.AudioHeader audio, log.log(Level.WARNING, I.SPU_ADPCM_CORRUPTED(demux.getStartSector(), _audDecoder.getSampleFramesWritten())); if (_audioListener != null) { - Fraction presentationSector = new Fraction(audio.getPresentationSample(), SAMPLES_PER_SECTOR) + Fraction presentationSector = new Fraction(audio.getPresentationSampleFrame(), SAMPLE_FRAMES_PER_SECTOR) .add(_iAbsoluteInitialFramePresentationSector); - _audioListener.audioPacketComplete(new DecodedAudioPacket(-1, CRUSADER_AUDIO_FORMAT, - presentationSector, - audioBuffer.toByteArray()), log); + DecodedAudioPacket packet = new DecodedAudioPacket(-1, CRUSADER_AUDIO_FORMAT, + presentationSector, + audioBuffer.toByteArray()); + _audioListener.audioPacketComplete(packet, log); } diff --git a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderSectorToCrusaderPacket.java b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderSectorToCrusaderPacket.java index 984ad37..623917e 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/CrusaderSectorToCrusaderPacket.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/CrusaderSectorToCrusaderPacket.java @@ -43,6 +43,7 @@ import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.DemuxPushInputStream; @@ -62,11 +63,13 @@ public class CrusaderSectorToCrusaderPacket { public interface PacketListener { void frame(@Nonnull CrusaderPacketHeaderReader.VideoHeader frame, @Nonnull DemuxedData demux, - @Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; void audio(@Nonnull CrusaderPacketHeaderReader.AudioHeader audio, @Nonnull DemuxedData demux, - @Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; } @CheckForNull @@ -91,7 +94,7 @@ public void setListener(@CheckForNull PacketListener listener) { /** Returns if the sector was accepted by this movie. * A new {@link CrusaderSectorToCrusaderPacket} should be created for * each movie. */ - public boolean sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log) { + public boolean sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log) throws LoggedFailure { if (_iPrevCrusaderSector != -1) { if (sector.getCrusaderSectorNumber() < _iPrevCrusaderSector) return false; @@ -120,13 +123,13 @@ private void addPiece(@Nonnull CrusaderDemuxPiece piece) { } /** Tells this to finish off the video and flush any remaining data. */ - public void endVideo(@Nonnull ILocalizedLogger log) { + public void endVideo(@Nonnull ILocalizedLogger log) throws LoggedFailure { _stream.close(); read(log); _stream = null; } - private void read(@Nonnull ILocalizedLogger log) { + private void read(@Nonnull ILocalizedLogger log) throws LoggedFailure { try { while (true) { if (_header == null) { diff --git a/jpsxdec/src/jpsxdec/modules/crusader/DemuxedCrusaderFrame.java b/jpsxdec/src/jpsxdec/modules/crusader/DemuxedCrusaderFrame.java index 77ed558..294994b 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/DemuxedCrusaderFrame.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/DemuxedCrusaderFrame.java @@ -50,6 +50,7 @@ import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.video.IDemuxedFrame; import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.DemuxedData; import jpsxdec.util.Fraction; @@ -79,6 +80,10 @@ public DemuxedCrusaderFrame(int iWidth, int iHeight, _iPresentationSector = iPresentationSector; } + public @CheckForNull MdecInputStream getCustomFrameMdecStream() { + return null; + } + public @Nonnull byte[] copyDemuxData() { return _demux.copyDemuxData(); } @@ -141,6 +146,7 @@ public void printSectors(@Nonnull PrintStream ps) { * @throws IllegalArgumentException * if {@code abNewDemux.length > } {@link #getDemuxSize()} */ + @Override public void writeToSectors(@Nonnull byte[] abNewDemux, int iUsedSize_ignore, int iMdecCodeCount_ignore, @Nonnull CdFileSectorReader cd, diff --git a/jpsxdec/src/jpsxdec/modules/crusader/DiscIndexerCrusader.java b/jpsxdec/src/jpsxdec/modules/crusader/DiscIndexerCrusader.java index 069aae2..f098eec 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/DiscIndexerCrusader.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/DiscIndexerCrusader.java @@ -52,6 +52,7 @@ import jpsxdec.discitems.DiscItem; import jpsxdec.discitems.SerializedDiscItem; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.indexing.DiscIndex; import jpsxdec.indexing.DiscIndexer; @@ -172,6 +173,7 @@ private static class VidBuilder implements CrusaderSectorToCrusaderPacket.Packet public VidBuilder(@Nonnull ILocalizedLogger errLog, @Nonnull SectorCrusader vidSect) + throws LoggedFailure { _errLog = errLog; _iStartSector = _iEndSector = vidSect.getSectorNumber(); @@ -181,7 +183,7 @@ public VidBuilder(@Nonnull ILocalizedLogger errLog, /** Returns if the supplied sector is part of this movie. If not, * end this movie and start a new one. */ - public boolean feedSector(@Nonnull SectorCrusader sector) { + public boolean feedSector(@Nonnull SectorCrusader sector) throws LoggedFailure { if (!_cs2cp.sectorRead(sector, _errLog)) { return false; } @@ -230,7 +232,7 @@ public void audio(@Nonnull CrusaderPacketHeaderReader.AudioHeader audio, // so pick an initial presentation sector a little before when the next // frame should be presented (-60) if (_iInitialFramePresentationSector < 0) { - _iInitialFramePresentationSector = (audio.getPresentationSample() / CrusaderPacketToFrameAndAudio.SAMPLES_PER_SECTOR) - 60; + _iInitialFramePresentationSector = (audio.getPresentationSampleFrame() / CrusaderPacketToFrameAndAudio.SAMPLE_FRAMES_PER_SECTOR) - 60; if (_iInitialFramePresentationSector < 0) // don't start before the start of the movie _iInitialFramePresentationSector = 0; else if (_iInitialFramePresentationSector > 0) @@ -239,7 +241,9 @@ else if (_iInitialFramePresentationSector > 0) _iSoundUnitCount += audio.getByteSize() / 2 / 16; } - public @CheckForNull DiscItemCrusader endOfMovie(@Nonnull CdFileSectorReader cd) { + public @CheckForNull DiscItemCrusader endOfMovie(@Nonnull CdFileSectorReader cd) + throws LoggedFailure + { _cs2cp.endVideo(_errLog); if (_indexSectorFrameNumberBuilder == null) // never received a frame @@ -287,7 +291,9 @@ public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { s2cs.setListener(this); } - public void sectorRead(@Nonnull SectorCrusader vidSect, @Nonnull ILocalizedLogger log) { + public void sectorRead(@Nonnull SectorCrusader vidSect, @Nonnull ILocalizedLogger log) + throws LoggedFailure + { if (_currentStream != null) { boolean blnAccepted = _currentStream.feedSector(vidSect); if (!blnAccepted) { @@ -302,7 +308,7 @@ public void sectorRead(@Nonnull SectorCrusader vidSect, @Nonnull ILocalizedLogge } } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + public void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure { if (_currentStream != null) { DiscItemCrusader vid = _currentStream.endOfMovie(getCd()); if (vid != null) diff --git a/jpsxdec/src/jpsxdec/modules/crusader/DiscItemCrusader.java b/jpsxdec/src/jpsxdec/modules/crusader/DiscItemCrusader.java index d1b2ebc..fdfa123 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/DiscItemCrusader.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/DiscItemCrusader.java @@ -38,34 +38,29 @@ package jpsxdec.modules.crusader; import java.util.Arrays; -import java.util.Date; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.sound.sampled.AudioFormat; import jpsxdec.cdreaders.CdFileSectorReader; import jpsxdec.discitems.SerializedDiscItem; -import jpsxdec.i18n.I; -import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; -import jpsxdec.modules.player.MediaPlayer; import jpsxdec.modules.sharedaudio.DecodedAudioPacket; -import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; import jpsxdec.modules.video.Dimensions; -import jpsxdec.modules.video.DiscItemVideoStream; import jpsxdec.modules.video.IDemuxedFrame; -import jpsxdec.modules.video.ISectorClaimToDemuxedFrame; import jpsxdec.modules.video.framenumber.FrameNumber; import jpsxdec.modules.video.framenumber.HeaderFrameNumber; import jpsxdec.modules.video.framenumber.IFrameNumberFormatterWithHeader; import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; +import jpsxdec.modules.video.packetbased.SectorClaimToAudioAndFrame; +import jpsxdec.modules.video.packetbased.DiscItemPacketBasedVideoStream; import jpsxdec.util.Fraction; -import jpsxdec.util.player.PlayController; /** Crusader: No Remorse audio/video stream. */ -public class DiscItemCrusader extends DiscItemVideoStream { +public class DiscItemCrusader extends DiscItemPacketBasedVideoStream { public static final String TYPE_ID = "Crusader"; private static final Fraction SECTORS_PER_FRAME = new Fraction(10); @@ -75,11 +70,9 @@ public class DiscItemCrusader extends DiscItemVideoStream { private final HeaderFrameNumber.Format _headerFrameNumberFormat; private static final String INITIAL_PRES_SECTOR_KEY = "Initial presentation sector"; + /** Should normally be 0 unless the first several sectors of the video are missing for some reason. */ private final int _iRelativeInitialFramePresentationSector; - private static final String SOUND_UNIT_COUNT_KEY = "Sound unit count"; - private final int _iSoundUnitCount; - public DiscItemCrusader(@Nonnull CdFileSectorReader cd, int iStartSector, int iEndSector, @Nonnull Dimensions dim, @@ -88,10 +81,9 @@ public DiscItemCrusader(@Nonnull CdFileSectorReader cd, int iInitialPresentationSector, int iSoundUnitCount) { - super(cd, iStartSector, iEndSector, dim, sectorIndexFrameNumberFormat); + super(cd, iStartSector, iEndSector, dim, sectorIndexFrameNumberFormat, iSoundUnitCount); _headerFrameNumberFormat = headerFrameNumberFormat; _iRelativeInitialFramePresentationSector = iInitialPresentationSector; - _iSoundUnitCount = iSoundUnitCount; } public DiscItemCrusader(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscItem fields) @@ -99,7 +91,6 @@ public DiscItemCrusader(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscI { super(cd, fields); _headerFrameNumberFormat = new HeaderFrameNumber.Format(fields); - _iSoundUnitCount = fields.getInt(SOUND_UNIT_COUNT_KEY); _iRelativeInitialFramePresentationSector = fields.getInt(INITIAL_PRES_SECTOR_KEY); } @@ -107,7 +98,6 @@ public DiscItemCrusader(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscI public @Nonnull SerializedDiscItem serialize() { SerializedDiscItem serial = super.serialize(); _headerFrameNumberFormat.serialize(serial); - serial.addNumber(SOUND_UNIT_COUNT_KEY, _iSoundUnitCount); serial.addNumber(INITIAL_PRES_SECTOR_KEY, _iRelativeInitialFramePresentationSector); return serial; } @@ -117,6 +107,11 @@ public DiscItemCrusader(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscI return TYPE_ID; } + @Override + public boolean hasIndependentBitstream() { + return true; + } + @Override public @Nonnull FrameNumber getStartFrame() { return _headerFrameNumberFormat.getStartFrame(_indexSectorFrameNumberFormat); @@ -133,30 +128,18 @@ public FrameNumber getEndFrame() { } @Override - public @Nonnull ILocalizedMessage getInterestingDescription() { - int iFrames = getFrameCount(); - Date secs = new Date(0, 0, 0, 0, 0, Math.max(iFrames / FPS, 1)); - return I.GUI_CRUSADER_VID_DETAILS(getWidth() ,getHeight(), iFrames, FPS, secs); + protected double getPacketBasedFpsInterestingDescription() { + return FPS; } - @Override - public @Nonnull VideoSaverBuilderCrusader makeSaverBuilder() { - return new VideoSaverBuilderCrusader(this); - } - - @Override - public int getDiscSpeed() { - return 2; // pretty sure it plays back at 2x - } - @Override public @Nonnull Fraction getSectorsPerFrame() { return SECTORS_PER_FRAME; } @Override - public int getAbsolutePresentationStartSector() { - return getStartSector(); + public int getAudioSampleFramesPerSecond() { + return CrusaderPacketToFrameAndAudio.CRUSADER_SAMPLE_FRAMES_PER_SECOND; } @Override @@ -165,22 +148,15 @@ public double getApproxDuration() { } @Override - public @Nonnull Demuxer makeDemuxer() { - return makeDemuxer(1.0); - } - - public @Nonnull Demuxer makeDemuxer(double dblVolume) { + public @Nonnull SectorClaimToAudioAndFrame makeAudioVideoDemuxer(double dblVolume) { return new Demuxer(dblVolume, _headerFrameNumberFormat.makeFormatter(_indexSectorFrameNumberFormat)); } - @Override - public @Nonnull PlayController makePlayController() { - Demuxer demuxer = makeDemuxer(); - return new PlayController(new MediaPlayer(this, demuxer, demuxer, getStartSector(), getEndSector())); - } - - public class Demuxer implements ISectorClaimToDemuxedFrame, ISectorAudioDecoder, - SectorClaimToSectorCrusader.Listener, + /* SectorClaimSystem -> CrusaderSectorToCrusaderPacket -> CrusaderPacketToFrameAndAudio -> IDemuxedFrame + * -> DecodedAudioPacket + */ + public class Demuxer extends SectorClaimToAudioAndFrame + implements SectorClaimToSectorCrusader.Listener, CrusaderPacketToFrameAndAudio.FrameListener { @@ -215,15 +191,17 @@ public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { s2cs.setRangeLimit(getStartSector(), getEndSector()); } - public void sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log) { + public void sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log) + throws LoggedFailure + { _cs2cp.sectorRead(sector, log); } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + public void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure { _cs2cp.endVideo(log); } - public void frameComplete(@Nonnull DemuxedCrusaderFrame frame, @Nonnull ILocalizedLogger log) { + public void frameComplete(@Nonnull DemuxedCrusaderFrame frame, @Nonnull ILocalizedLogger log) throws LoggedFailure { frame.setFrame(_frameNumberFormatter.next(frame.getStartSector(), frame.getHeaderFrameNumber(), log)); if (_listener != null) _listener.frameComplete(frame); @@ -260,7 +238,7 @@ public int getEndSector() { } public int getSampleFramesPerSecond() { - return CrusaderPacketToFrameAndAudio.CRUSADER_SAMPLES_PER_SECOND; + return CrusaderPacketToFrameAndAudio.CRUSADER_SAMPLE_FRAMES_PER_SECOND; } public int getDiscSpeed() { diff --git a/jpsxdec/src/jpsxdec/modules/crusader/SectorClaimToSectorCrusader.java b/jpsxdec/src/jpsxdec/modules/crusader/SectorClaimToSectorCrusader.java index 988378e..564e2f5 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/SectorClaimToSectorCrusader.java +++ b/jpsxdec/src/jpsxdec/modules/crusader/SectorClaimToSectorCrusader.java @@ -40,6 +40,7 @@ import java.io.IOException; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.IOIterator; @@ -48,8 +49,10 @@ public class SectorClaimToSectorCrusader extends SectorClaimSystem.SectorClaimer { public interface Listener { - void sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log); - void endOfSectors(@Nonnull ILocalizedLogger log); + void sectorRead(@Nonnull SectorCrusader sector, @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void endOfSectors(@Nonnull ILocalizedLogger log) + throws LoggedFailure; } @CheckForNull @@ -67,7 +70,7 @@ public void setListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { if (cs.isClaimed()) return; @@ -77,13 +80,25 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, cs.claim(sector); - if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) - _listener.sectorRead(sector, log); + if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) { + try { + _listener.sectorRead(sector, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } - public void endOfSectors(@Nonnull ILocalizedLogger log) { - if (_listener != null) - _listener.endOfSectors(log); + public void endOfSectors(@Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + if (_listener != null) { + try { + _listener.endOfSectors(log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } } diff --git a/jpsxdec/src/jpsxdec/modules/dredd/DemuxedDreddFrame.java b/jpsxdec/src/jpsxdec/modules/dredd/DemuxedDreddFrame.java index 8a0b074..9063f64 100644 --- a/jpsxdec/src/jpsxdec/modules/dredd/DemuxedDreddFrame.java +++ b/jpsxdec/src/jpsxdec/modules/dredd/DemuxedDreddFrame.java @@ -46,6 +46,7 @@ import jpsxdec.modules.video.IDemuxedFrame; import jpsxdec.modules.video.framenumber.FrameNumber; import jpsxdec.modules.video.sectorbased.SectorBasedFrameReplace; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.DemuxedData; import jpsxdec.util.Fraction; @@ -69,6 +70,10 @@ public DemuxedDreddFrame(@Nonnull DemuxedData demux, int iHeig _iHeight = iHeight; } + public @CheckForNull MdecInputStream getCustomFrameMdecStream() { + return null; + } + public int getHeight() { return _iHeight; } diff --git a/jpsxdec/src/jpsxdec/modules/dredd/DiscItemDreddVideoStream.java b/jpsxdec/src/jpsxdec/modules/dredd/DiscItemDreddVideoStream.java index 5ba2f7f..912c449 100644 --- a/jpsxdec/src/jpsxdec/modules/dredd/DiscItemDreddVideoStream.java +++ b/jpsxdec/src/jpsxdec/modules/dredd/DiscItemDreddVideoStream.java @@ -47,6 +47,7 @@ import jpsxdec.discitems.DiscItem; import jpsxdec.discitems.SerializedDiscItem; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.DebugLogger; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.IIdentifiedSector; @@ -91,6 +92,11 @@ public DiscItemDreddVideoStream(@Nonnull CdFileSectorReader cd, @Nonnull Seriali return TYPE_ID; } + @Override + public boolean hasIndependentBitstream() { + return true; + } + @Override public int getParentRating(@Nonnull DiscItem child) { if (!(child instanceof DiscItemXaAudioStream)) @@ -191,7 +197,7 @@ public void setFrameListener(@Nonnull IDemuxedFrame.Listener listener) { _listener = listener; } - public void frameComplete(@Nonnull DemuxedDreddFrame frame, @Nonnull ILocalizedLogger log) { + public void frameComplete(@Nonnull DemuxedDreddFrame frame, @Nonnull ILocalizedLogger log) throws LoggedFailure { FrameNumber fn = _indexSectorFrameNumberFormatter.next(frame.getStartSector(), log); frame.setFrame(fn); if (_listener != null) diff --git a/jpsxdec/src/jpsxdec/modules/dredd/DreddDemuxer.java b/jpsxdec/src/jpsxdec/modules/dredd/DreddDemuxer.java index 74e4d6a..dba312e 100644 --- a/jpsxdec/src/jpsxdec/modules/dredd/DreddDemuxer.java +++ b/jpsxdec/src/jpsxdec/modules/dredd/DreddDemuxer.java @@ -48,6 +48,7 @@ import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv3; +import jpsxdec.psxvideo.bitstreams.StrHeader; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.DemuxedData; @@ -73,30 +74,30 @@ public class DreddDemuxer { return null; // only first chunk can we check for bitstream header - BitStreamUncompressor.Type bsuType = hasBitstreamHeader(cdSector, 4); + StrHeader strHeader = hasBitstreamHeader(cdSector, 4); int iFirstHeaderSize; - if (bsuType != null) { + if (strHeader != null) { iFirstHeaderSize = 4; } else { - bsuType = hasBitstreamHeader(cdSector, 44); - if (bsuType != null) + strHeader = hasBitstreamHeader(cdSector, 44); + if (strHeader != null) iFirstHeaderSize = 44; else return null; } - return new DreddDemuxer(bsuType, new SectorDreddVideo(cdSector, iChunk, iFirstHeaderSize)); + return new DreddDemuxer(strHeader, new SectorDreddVideo(cdSector, iChunk, iFirstHeaderSize)); } @Nonnull - private final BitStreamUncompressor.Type _bsuType; + private final StrHeader _strHeader; @Nonnull private final ArrayList _sectors = new ArrayList(MAX_CHUNKS_PER_FRAME); - public DreddDemuxer(@Nonnull BitStreamUncompressor.Type bsuType, + public DreddDemuxer(@Nonnull StrHeader strHeader, @Nonnull SectorDreddVideo firstDreddFrameSector) { - _bsuType = bsuType; + _strHeader = strHeader; _sectors.add(firstDreddFrameSector); } @@ -155,7 +156,7 @@ public FrameSectors(DemuxedDreddFrame frame, List sectors) { } byte[] abDemuxBuffer = baos.toByteArray(); - if (!checkHeight(abDemuxBuffer, _bsuType)) { + if (!checkHeight(abDemuxBuffer, _strHeader)) { LOG.log(Level.WARNING, "Possible Dredd frame failed bitstream check starting with sector {0}", _sectors.get(0)); return null; @@ -199,26 +200,20 @@ private static boolean commonSectorCheck(@Nonnull CdSector cdSector) { return true; } - /** Returns if this is a v2 or v3 frame. Be sure to keep in sync with - * {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2#checkHeader(byte[])} - * and - * {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv3#checkHeader(byte[])}. - */ - private static @CheckForNull BitStreamUncompressor.Type hasBitstreamHeader(@Nonnull CdSector cdSector, int iOfs) { + /** Returns if this is a v2 or v3 frame. */ + private static @CheckForNull StrHeader hasBitstreamHeader(@Nonnull CdSector cdSector, int iOfs) { if (cdSector.getCdUserDataSize() + iOfs < 8) return null; byte[] abHeader = new byte[8]; cdSector.getCdUserDataCopy(iOfs, abHeader, 0, abHeader.length); - BitStreamUncompressor_STRv2.StrV2Header v2 = - new BitStreamUncompressor_STRv2.StrV2Header(abHeader, abHeader.length); + StrHeader v2 = new BitStreamUncompressor_STRv2.StrV2Header(abHeader, abHeader.length); if (v2.isValid()) - return BitStreamUncompressor.Type.STRv2; - BitStreamUncompressor_STRv3.StrV3Header v3 = - new BitStreamUncompressor_STRv3.StrV3Header(abHeader, abHeader.length); + return v2; + StrHeader v3 = new BitStreamUncompressor_STRv3.StrV3Header(abHeader, abHeader.length); if (v3.isValid()) - return BitStreamUncompressor.Type.STRv3; + return v3; return null; } @@ -230,10 +225,10 @@ private static boolean commonSectorCheck(@Nonnull CdSector cdSector) { /** Uncompresses the bitstream by a minium amount to ensure it is valid. */ private static boolean checkHeight(@Nonnull byte[] abFullFrame, - @Nonnull BitStreamUncompressor.Type bsuType) + @Nonnull StrHeader strHeader) { try { - BitStreamUncompressor bsu = bsuType.makeNew(abFullFrame); + BitStreamUncompressor bsu = strHeader.makeNew(abFullFrame); bsu.skipMacroBlocks(FRAME_WIDTH, FRAME_HEIGHT_B); return true; } catch (MdecException.EndOfStream ex) { diff --git a/jpsxdec/src/jpsxdec/modules/dredd/SectorClaimToDreddFrame.java b/jpsxdec/src/jpsxdec/modules/dredd/SectorClaimToDreddFrame.java index 0e4416b..28f52e2 100644 --- a/jpsxdec/src/jpsxdec/modules/dredd/SectorClaimToDreddFrame.java +++ b/jpsxdec/src/jpsxdec/modules/dredd/SectorClaimToDreddFrame.java @@ -45,6 +45,7 @@ import javax.annotation.Nonnull; import jpsxdec.cdreaders.CdSector; import jpsxdec.cdreaders.CdSectorXaSubHeader; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.IOIterator; @@ -55,7 +56,8 @@ public class SectorClaimToDreddFrame extends SectorClaimSystem.SectorClaimer { private static final Logger LOG = Logger.getLogger(SectorClaimToDreddFrame.class.getName()); public interface Listener { - void frameComplete(@Nonnull DemuxedDreddFrame frame, @Nonnull ILocalizedLogger log); + void frameComplete(@Nonnull DemuxedDreddFrame frame, @Nonnull ILocalizedLogger log) + throws LoggedFailure; void videoBreak(@Nonnull ILocalizedLogger log); void endOfSectors(@Nonnull ILocalizedLogger log); } @@ -118,9 +120,13 @@ public void setListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { - beforeEofCheck(cs, peekIt, log); + try { + beforeEofCheck(cs, peekIt, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } // after processing the current sector, always check the EOF flag // the only way to know a video ends is by the EOF marker CdSectorXaSubHeader sh = cs.getSector().getSubHeader(); @@ -137,7 +143,7 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, private void beforeEofCheck(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, LoggedFailure { // claimed? ignore if (cs.isClaimed()) diff --git a/jpsxdec/src/jpsxdec/modules/granturismo/GranTurismoDemuxer.java b/jpsxdec/src/jpsxdec/modules/granturismo/GranTurismoDemuxer.java index 762b173..3563039 100644 --- a/jpsxdec/src/jpsxdec/modules/granturismo/GranTurismoDemuxer.java +++ b/jpsxdec/src/jpsxdec/modules/granturismo/GranTurismoDemuxer.java @@ -43,8 +43,8 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.i18n.log.ILocalizedLogger; -import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; import jpsxdec.modules.video.sectorbased.DemuxedFrameWithNumberAndDims; +import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; import jpsxdec.modules.video.sectorbased.SectorBasedFrameBuilder; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_Iki; import jpsxdec.util.DemuxedData; diff --git a/jpsxdec/src/jpsxdec/modules/granturismo/SectorGTVideo.java b/jpsxdec/src/jpsxdec/modules/granturismo/SectorGTVideo.java index 30f48f4..6f9314c 100644 --- a/jpsxdec/src/jpsxdec/modules/granturismo/SectorGTVideo.java +++ b/jpsxdec/src/jpsxdec/modules/granturismo/SectorGTVideo.java @@ -90,13 +90,13 @@ public SectorGTVideo(@Nonnull CdSector cdSector) { if (_header.lngMagic != GT_MAGIC) return; - if (!_header.isChunkNumberStandard()) + if (!_header.hasStandardChunkNumber()) return; - if (!_header.isChunksInFrameStandard()) + if (!_header.hasStandardChunksInFrame()) return; if (_header.iFrameNumber < 1) return; - if (!_header.isUsedDemuxSizeStandard()) + if (!_header.hasStandardUsedDemuxSize()) return; _iTotalFrames = cdSector.readSInt16LE(16); if (_iTotalFrames < 1) diff --git a/jpsxdec/src/jpsxdec/modules/iso9660/DiscItemISO9660File.java b/jpsxdec/src/jpsxdec/modules/iso9660/DiscItemISO9660File.java index ac43a21..c1e8ab1 100644 --- a/jpsxdec/src/jpsxdec/modules/iso9660/DiscItemISO9660File.java +++ b/jpsxdec/src/jpsxdec/modules/iso9660/DiscItemISO9660File.java @@ -76,6 +76,7 @@ import jpsxdec.i18n.exception.LocalizedDeserializationFail; import jpsxdec.i18n.exception.LocalizedFileNotFoundException; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.util.ArgParser; import jpsxdec.util.IO; @@ -317,6 +318,7 @@ public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File outputDir) throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); File outputFile = new File(outputDir, getPath().getPath()); try { @@ -364,11 +366,11 @@ public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File outputDir) pl.progressEnd(); } - public void printSelectedOptions(@Nonnull FeedbackStream fbs) { + public void printSelectedOptions(@Nonnull ILocalizedLogger log) { if (getSaveRaw()) - fbs.println(I.CMD_ISOFILE_SAVING_RAW(getRawSectorSize())); + log.log(Level.INFO, I.CMD_ISOFILE_SAVING_RAW(getRawSectorSize())); else - fbs.println(I.CMD_ISOFILE_SAVING_2048()); + log.log(Level.INFO, I.CMD_ISOFILE_SAVING_2048()); } } diff --git a/jpsxdec/src/jpsxdec/modules/player/AudioPlayerSectorTimedWriter.java b/jpsxdec/src/jpsxdec/modules/player/AudioPlayerSectorTimedWriter.java index 1ae5f09..40e8e38 100644 --- a/jpsxdec/src/jpsxdec/modules/player/AudioPlayerSectorTimedWriter.java +++ b/jpsxdec/src/jpsxdec/modules/player/AudioPlayerSectorTimedWriter.java @@ -37,49 +37,58 @@ package jpsxdec.modules.player; +import java.io.IOException; +import java.io.OutputStream; import javax.annotation.Nonnull; -import javax.sound.sampled.AudioFormat; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.sharedaudio.DecodedAudioPacket; import jpsxdec.modules.video.save.AudioSync; +import jpsxdec.util.IO; -/** Receives audio packets and sends them to the inner {@link MediaPlayer} +/** Receives audio packets and sends them to the inner {@link OutputStream} * making sure to add silence if there is any gap in the packets. * This ensures the audio stays in sync with the playback. */ public class AudioPlayerSectorTimedWriter implements DecodedAudioPacket.Listener { @Nonnull - private final MediaPlayer _player; + private final OutputStream _audioOut; @Nonnull private final AudioSync _audioSync; private long _lngSampleFramesWritten = 0; - public AudioPlayerSectorTimedWriter(@Nonnull MediaPlayer player, int iMovieStartSector, - int iSectorsPerSecond, int iSamplesPerSecond) + public AudioPlayerSectorTimedWriter(@Nonnull OutputStream audioOutputStream, + int iMovieStartSector, + int iSectorsPerSecond, + int iSamplesPerSecond) { - _player = player; - AudioFormat fmt = _player.getAudioFormat(); - if (fmt == null) - throw new IllegalArgumentException("Media player without audio passed to AudioPlayerSectorTimedWriter"); + _audioOut = audioOutputStream; _audioSync = new AudioSync(iMovieStartSector, iSectorsPerSecond, iSamplesPerSecond); } public void audioPacketComplete(@Nonnull DecodedAudioPacket packet, @Nonnull ILocalizedLogger log) { - // already confirmed that _player has an audio format - if (!packet.getAudioFormat().matches(_player.getAudioFormat())) - throw new IllegalArgumentException("Incompatable audio format."); + try { + long lngSampleFrameDiff = _audioSync.calculateAudioToCatchUp(packet.getPresentationSector(), _lngSampleFramesWritten); + if (lngSampleFrameDiff > 0) { + System.out.println("Audio out of sync " + lngSampleFrameDiff + " samples, adding silence."); + long lngSilentBytes = lngSampleFrameDiff * packet.getAudioFormat().getFrameSize(); + while (lngSilentBytes > 0) { + int iToWrite = (int) Math.min((long)Integer.MAX_VALUE, lngSilentBytes); + IO.writeZeros(_audioOut, iToWrite); + lngSilentBytes -= iToWrite; + } + // TODO move this inside the loop in case there is an error it will keep the inner state correct + _lngSampleFramesWritten += lngSampleFrameDiff; + } - long lngSampleFrameDiff = _audioSync.calculateAudioToCatchUp(packet.getPresentationSector(), _lngSampleFramesWritten); - if (lngSampleFrameDiff > 0) { - System.out.println("Audio out of sync " + lngSampleFrameDiff + " samples, adding silence."); - _player.writeSilence(lngSampleFrameDiff); - _lngSampleFramesWritten += lngSampleFrameDiff; - } - - _lngSampleFramesWritten += packet.getSampleFrameCount(); + _lngSampleFramesWritten += packet.getSampleFrameCount(); - byte[] abData = packet.getData(); - _player.writeAudio(abData, 0, abData.length); + byte[] abData = packet.getData(); + //System.out.println("Sending " + abData.length + " bytes of audio"); + _audioOut.write(abData); + } catch (IOException ex) { + // wrap the exception and have the MediaPlayer catch it outside the pipeline + throw new WrapIOException(ex); + } } } diff --git a/jpsxdec/src/jpsxdec/modules/player/MediaPlayer.java b/jpsxdec/src/jpsxdec/modules/player/MediaPlayer.java index a7803e9..a3c1516 100644 --- a/jpsxdec/src/jpsxdec/modules/player/MediaPlayer.java +++ b/jpsxdec/src/jpsxdec/modules/player/MediaPlayer.java @@ -37,10 +37,7 @@ package jpsxdec.modules.player; -import java.util.logging.Logger; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import javax.sound.sampled.AudioFormat; import jpsxdec.cdreaders.CdFileSectorReader; import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.i18n.exception.LoggedFailure; @@ -53,40 +50,38 @@ import jpsxdec.modules.video.IDemuxedFrame; import jpsxdec.modules.video.ISectorClaimToDemuxedFrame; import jpsxdec.modules.video.framenumber.FormattedFrameNumber; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.modules.video.save.AutowireVDP; +import jpsxdec.modules.video.save.Frame2Bitstream; import jpsxdec.modules.video.save.VDP; import jpsxdec.psxvideo.mdec.MdecDecoder; import jpsxdec.psxvideo.mdec.MdecDecoder_int; import jpsxdec.psxvideo.mdec.idct.SimpleIDCT; import jpsxdec.util.Fraction; -import jpsxdec.util.player.AudioVideoReader; -import jpsxdec.util.player.IDecodableFrame; -import jpsxdec.util.player.ObjectPool; +import jpsxdec.util.player.IFrameProcessor; +import jpsxdec.util.player.IMediaDataReader; +import jpsxdec.util.player.IPreprocessedFrameWriter; +import jpsxdec.util.player.PlayController; +import jpsxdec.util.player.StopPlayingException; -/** Holds all the class implementations that the {@link jpsxdec.util.player} +/** Holds all the class implementations that the {@link jpsxdec.util.player} * framework needs to playback PlayStation audio and/or video. */ -public class MediaPlayer extends AudioVideoReader implements IDemuxedFrame.Listener { - - private static final Logger LOG = Logger.getLogger(MediaPlayer.class.getName()); - - private static final boolean DEBUG = false; +public class MediaPlayer implements IMediaDataReader { private final int _iMovieStartSector; private final int _iMovieEndSector; @Nonnull private final CdFileSectorReader _cdReader; - private final double _dblDuration; + + @Nonnull + private final PlayController _controller; + + private final AutowireVDP _demuxAutowire = new AutowireVDP(); + private final AutowireVDP _decodeAutowire = new AutowireVDP(); //---------------------------------------------------------- - @CheckForNull - private final DiscItemVideoStream _vid; - private int _iSectorsPerSecond; - @CheckForNull - private final VDP.Bitstream2Mdec _b2m; - @CheckForNull - private final VDP.Mdec2Decoded _m2d; - @CheckForNull - private final ISectorClaimToDemuxedFrame _demuxer; + private final int _iSectorsPerSecond; public MediaPlayer(@Nonnull DiscItemVideoStream vid, @Nonnull ISectorClaimToDemuxedFrame demuxer) { this(vid, demuxer, vid.getStartSector(), vid.getEndSector()); @@ -96,34 +91,11 @@ public MediaPlayer(@Nonnull DiscItemVideoStream vid, @Nonnull ISectorClaimToDemu public MediaPlayer(@Nonnull DiscItemVideoStream vid, @Nonnull ISectorClaimToDemuxedFrame demuxer, int iSectorStart, int iSectorEnd) { - _cdReader = vid.getSourceCd(); - _iMovieStartSector = iSectorStart; - _iMovieEndSector = iSectorEnd; - if (vid.getDiscSpeed() == 1) { - _iSectorsPerSecond = 75; - } else { - // if disc speed is unknown, assume 2x - _iSectorsPerSecond = 150; - } - _dblDuration = vid.getApproxDuration(); - - _vid = vid; - _m2d = new VDP.Mdec2Decoded(new MdecDecoder_int(new SimpleIDCT(), - vid.getWidth(), - vid.getHeight()), - DebugLogger.Log); - _b2m = new VDP.Bitstream2Mdec(_m2d); - _demuxer = demuxer; - _demuxer.setFrameListener(this); + this(vid, demuxer, null, iSectorStart, iSectorEnd); } //----------------------------------------------------------------------- - @CheckForNull - private ISectorAudioDecoder _audioDecoder; - @CheckForNull - private AudioPlayerSectorTimedWriter _audioOut; - public MediaPlayer(@Nonnull DiscItemAudioStream aud) { _cdReader = aud.getSourceCd(); _iMovieStartSector = aud.getStartSector(); @@ -134,180 +106,159 @@ public MediaPlayer(@Nonnull DiscItemAudioStream aud) { // if disc speed is unknown, assume 2x _iSectorsPerSecond = 150; } - _dblDuration = aud.getApproxDuration(); + + ISectorAudioDecoder audioDecoder = aud.makeDecoder(1.0); + _demuxAutowire.setAudioDecoder(audioDecoder); - _audioDecoder = aud.makeDecoder(1.0); - _audioOut = new AudioPlayerSectorTimedWriter(this, _iMovieStartSector, _iSectorsPerSecond, _audioDecoder.getSampleFramesPerSecond()); - _audioDecoder.setAudioListener(_audioOut); + _controller = new PlayController(audioDecoder.getOutputFormat()); + _controller.setReader(this); + audioDecoder.getAbsolutePresentationStartSector(); // <-- TODO check if it would be better to use this to align on initial presentation sector - // ignore video - _vid = null; - _b2m = null; - _m2d = null; - _demuxer = null; + AudioPlayerSectorTimedWriter audioWriter = new AudioPlayerSectorTimedWriter(_controller.getAudioOutputStream(), _iMovieStartSector, _iSectorsPerSecond, audioDecoder.getSampleFramesPerSecond()); + _demuxAutowire.setAudioPacketListener(audioWriter); } - + //---------------------------------------------------------- - public MediaPlayer(@Nonnull DiscItemVideoStream vid, + public MediaPlayer(@Nonnull DiscItemVideoStream vid, @Nonnull ISectorClaimToDemuxedFrame demuxer, - @Nonnull ISectorAudioDecoder audio, + @Nonnull ISectorAudioDecoder audioDecoder, // tell everyone this can't be null, but secretly allow it int iSectorStart, int iSectorEnd) { // do the video init - this(vid, demuxer, iSectorStart, iSectorEnd); - - if (audio.getDiscSpeed() == 1) { + _cdReader = vid.getSourceCd(); + _iMovieStartSector = iSectorStart; + _iMovieEndSector = iSectorEnd; + if (vid.getDiscSpeed() == 1) { _iSectorsPerSecond = 75; } else { // if disc speed is unknown, assume 2x _iSectorsPerSecond = 150; } - // manually init the audio - _audioDecoder = audio; - _audioOut = new AudioPlayerSectorTimedWriter(this, _iMovieStartSector, _iSectorsPerSecond, _audioDecoder.getSampleFramesPerSecond()); - _audioDecoder.setAudioListener(_audioOut); - } + _demuxAutowire.setMap(demuxer); - public void demuxThread() { + if (audioDecoder == null) { + _controller = new PlayController(vid.getWidth(), vid.getHeight()); + } else { + _controller = new PlayController(vid.getWidth(), vid.getHeight(), audioDecoder.getOutputFormat()); + + AudioPlayerSectorTimedWriter audioWriter = new AudioPlayerSectorTimedWriter(_controller.getAudioOutputStream(), _iMovieStartSector, _iSectorsPerSecond, audioDecoder.getSampleFramesPerSecond()); + _demuxAutowire.setAudioDecoder(audioDecoder); + _demuxAutowire.setAudioPacketListener(audioWriter); + } + + ProcessingThread pt = new ProcessingThread(vid.getWidth(), vid.getHeight()); + _controller.setVidProcressor(pt); + _decodeAutowire.setMap(pt); + _decodeAutowire.setDecodedListener(pt); + _decodeAutowire.setMap(new VDP.Mdec2Decoded(new MdecDecoder_int(new SimpleIDCT(), vid.getWidth(), vid.getHeight()), DebugLogger.Log)); + _decodeAutowire.setMap(new VDP.Bitstream2Mdec()); + _decodeAutowire.autowire(); + + vid.getAbsolutePresentationStartSector(); // <-- TODO check if it would be better to align on initial presentation sector + + _demuxAutowire.setFrameListener(new DemuxFrameToPlayerProcessor(_controller.getFrameWriter(), _iMovieStartSector, _iSectorsPerSecond)); + + _controller.setReader(this); + } + public void demuxThread(@Nonnull PlayController controller) throws StopPlayingException { try { final int iSectorLength = _iMovieEndSector - _iMovieStartSector + 1; SectorClaimSystem it = SectorClaimSystem.create(_cdReader, _iMovieStartSector, _iMovieEndSector); - if (_demuxer != null) - _demuxer.attachToSectorClaimer(it); - if (_audioDecoder != null) - _audioDecoder.attachToSectorClaimer(it); - for (int iSector = 0; it.hasNext() && stillPlaying(); iSector++) + _demuxAutowire.attachToSectorClaimer(it); + _demuxAutowire.autowire(); + + IIdentifiedSector identifiedSector; + for (int iSector = 0; it.hasNext() && !controller.isClosed(); iSector++) { - IIdentifiedSector identifiedSector = it.next(DebugLogger.Log).getClaimer(); - setReadProgress(iSector*100 / iSectorLength); + identifiedSector = it.next(DebugLogger.Log).getClaimer(); } - + it.close(DebugLogger.Log); + } catch (WrapIOException ex) { + if (ex.getCause() instanceof StopPlayingException) + throw (StopPlayingException)ex.getCause(); + else + throw new StopPlayingException(ex.getCause()); } catch (CdFileSectorReader.CdReadException ex) { - throw new RuntimeException(ex); + throw new StopPlayingException(ex); } } - public void frameComplete(@Nonnull IDemuxedFrame frame) { - StrFrame strFrame = _framePool.borrow(); - strFrame.init(frame.getDemuxSize(), frame.getFrame().getIndexNumber(), frame.getPresentationSector().subtract(_iMovieStartSector).asInt()); - strFrame.__abDemuxBuf = frame.copyDemuxData(); - writeFrame(strFrame); - } - - // ######################################################################### - // ######################################################################### - - public @CheckForNull AudioFormat getAudioFormat() { - if (_audioDecoder == null) - return null; - return _audioDecoder.getOutputFormat(); + public @Nonnull PlayController getPlayController() { + return _controller; } + private static class DemuxFrameToPlayerProcessor implements IDemuxedFrame.Listener { + @Nonnull + private final IPreprocessedFrameWriter _processor; + private final int _iAbsolutePresentationStartSector; + private final int _iSectorsPerSecond; - // ######################################################################### - // ######################################################################### - - private class DecodableFramePool extends ObjectPool { - - @Override - protected StrFrame createNewObject() { - if (DEBUG) System.err.println("Creating new pool object."); - return new StrFrame(); + public DemuxFrameToPlayerProcessor(@Nonnull IPreprocessedFrameWriter processor, + int iAbsolutePresentationStartSector, + int iSectorsPerSecond) + { + _processor = processor; + _iAbsolutePresentationStartSector = iAbsolutePresentationStartSector; + _iSectorsPerSecond = iSectorsPerSecond; } - } - private final DecodableFramePool _framePool = new DecodableFramePool(); - - - public boolean hasVideo() { - return _vid != null; - } + @Override + public void frameComplete(@Nonnull IDemuxedFrame frame) { - public int getVideoWidth() { - if (_vid == null) - throw new UnsupportedOperationException("Accessing video dimension for audio only player"); - return _vid.getWidth(); + long lngPresentationNanos = (long) ((frame.getPresentationSector().asDouble() - _iAbsolutePresentationStartSector) / _iSectorsPerSecond * 1000000000.); + try { + _processor.writeFrame(frame, lngPresentationNanos); + } catch (StopPlayingException ex) { + throw new WrapIOException(ex); + } + } } - public int getVideoHeight() { - if (_vid == null) - throw new UnsupportedOperationException("Accessing video dimension for audio only player"); - return _vid.getHeight(); - } - public double getDuration() { - return _dblDuration; - } + private static class ProcessingThread extends Frame2Bitstream implements IFrameProcessor, VDP.IDecodedListener { - private class StrFrame implements IDecodableFrame, VDP.IDecodedListener { + private final int _iWidth, _iHeight; - @CheckForNull - public byte[] __abDemuxBuf; - @CheckForNull - private FormattedFrameNumber __frameNum; - private int __iSectorFromStart; - @CheckForNull - private int[] __aiDrawHere; - - public void init(int iSize, @Nonnull FormattedFrameNumber frameNum, int iSectorFromStart) { - __iSectorFromStart = iSectorFromStart; - __frameNum = frameNum; - } + private int[] _aiDrawHere; - public long getPresentationTime() { - return (__iSectorFromStart * 1000000000L / _iSectorsPerSecond); + public ProcessingThread(int iWidth, int iHeight) { + super(FrameNumber.Type.Index); + _iWidth = iWidth; + _iHeight = iHeight; } - public void decodeVideo(@Nonnull int[] drawHere) { - // _md2 and _b2m should != null when processing frames - // if not, bad stuff should happen - _m2d.setDecoded(this); - __aiDrawHere = drawHere; + @Override + public void processFrame(@Nonnull IDemuxedFrame frame, int[] drawHere) { + // ideally we would have a buffer in this class where the decoded frame is written, then copy that into drawHere + // but we don't want an extra copy, so use this workaround + _aiDrawHere = drawHere; try { - // This will call _m2d which in turn will call decoded() - // __abDemuxBuf and __frameNum should have been initialied in init() - // The presentation sector is passed here, but it is not used directly, - // instead we use getPresentationTime() - _b2m.bitstream(__abDemuxBuf, __abDemuxBuf.length, __frameNum, new Fraction(__iSectorFromStart)); + frameComplete(frame); } catch (LoggedFailure ex) { - System.err.print("Frame "+__frameNum+' '+ex.getMessage()); - if (ex.getCause() != null && ex.getCause().getMessage() != null) - System.err.println(": " + ex.getCause().getMessage()); - else - System.err.println(); + System.err.println("Frame "+frame.getFrame()+" "+ex.getMessage()); } finally { - _m2d.setDecoded(null); - __aiDrawHere = null; + _aiDrawHere = null; } } - public void assertAcceptsDecoded(@Nonnull MdecDecoder decoder) {} - - public void decoded(@Nonnull MdecDecoder decoder, - @Nonnull FormattedFrameNumber frameNumber, - @Nonnull Fraction presentationSector_unused) - { - decoder.readDecodedRgb(getVideoWidth(), getVideoHeight(), __aiDrawHere); + @Override + public void decoded(@Nonnull MdecDecoder decoder, FormattedFrameNumber _ignoredFN, Fraction _ignoredPS) { + decoder.readDecodedRgb(_iWidth, _iHeight, _aiDrawHere); } - public void error(@Nonnull ILocalizedMessage errMsg, - @Nonnull FormattedFrameNumber frameNumber, - @Nonnull Fraction presentationSector_unused) - { + @Override + public void error(ILocalizedMessage errMsg, FormattedFrameNumber frameNumber, Fraction presentationSector) { System.err.println(errMsg.getEnglishMessage()); } - public void returnToPool() { - if (DEBUG) System.err.println("Returning object to pool."); - _framePool.giveBack(this); - } - + @Override + public void assertAcceptsDecoded(MdecDecoder decoder) {} } } diff --git a/jpsxdec/src/jpsxdec/modules/player/WrapIOException.java b/jpsxdec/src/jpsxdec/modules/player/WrapIOException.java new file mode 100644 index 0000000..d4dbc07 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/player/WrapIOException.java @@ -0,0 +1,54 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.player; + +import java.io.IOException; + +/** Catches an {@link IOException} and wraps it in a {@link RuntimeException} + * to be thrown later. */ +class WrapIOException extends RuntimeException { + + public WrapIOException(IOException cause) { + super(cause); + } + + @Override + public IOException getCause() { + return (IOException) super.getCause(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/DemuxPolicenautsFrame.java b/jpsxdec/src/jpsxdec/modules/policenauts/DemuxPolicenautsFrame.java new file mode 100644 index 0000000..b529349 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/DemuxPolicenautsFrame.java @@ -0,0 +1,116 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.PrintStream; +import java.util.Arrays; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; +import jpsxdec.util.Fraction; + + +public class DemuxPolicenautsFrame implements IDemuxedFrame { + private final int _iWidth, _iHeight; + private final SPacketData _data; + @Nonnull + private final FrameNumber _frameNumber; + @Nonnull + private final Fraction _presentationSector; + + public DemuxPolicenautsFrame(int iWidth, int iHeight, + @Nonnull SPacketData data, + @Nonnull FrameNumber frameNumber, + @Nonnull Fraction presentationSector) + { + _iWidth = iWidth; + _iHeight = iHeight; + _data = data; + _frameNumber = frameNumber; + _presentationSector = presentationSector; + } + + public int getWidth() { + return _iWidth; + } + + public int getHeight() { + return _iHeight; + } + + public @Nonnull FrameNumber getFrame() { + return _frameNumber; + } + + public int getStartSector() { + return _data.getStartSector(); + } + + public int getEndSector() { + return _data.getEndSector(); + } + + public @Nonnull Fraction getPresentationSector() { + return _presentationSector; + } + + public @CheckForNull MdecInputStream getCustomFrameMdecStream() { + return null; + } + + public int getDemuxSize() { + return _data.getData().length; + } + + public @Nonnull byte[] copyDemuxData() { + return Arrays.copyOfRange(_data.getData(), 0, getDemuxSize()); + } + + public void printSectors(PrintStream ps) { + // TODO? + } + + public void writeToSectors(byte[] abNewDemux, int iNewUsedSize, int iNewMdecCodeCount, CdFileSectorReader cd, ILocalizedLogger log) throws LoggedFailure { + throw new UnsupportedOperationException("Replacing Policenauts frames is not supported"); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/DiscIndexerPolicenauts.java b/jpsxdec/src/jpsxdec/modules/policenauts/DiscIndexerPolicenauts.java new file mode 100644 index 0000000..97c9ea3 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/DiscIndexerPolicenauts.java @@ -0,0 +1,165 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.util.Collection; +import java.util.logging.Level; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.discitems.DiscItem; +import jpsxdec.discitems.SerializedDiscItem; +import jpsxdec.i18n.I; +import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.indexing.DiscIndex; +import jpsxdec.indexing.DiscIndexer; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.video.Dimensions; +import jpsxdec.modules.video.framenumber.HeaderFrameNumber; +import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; + + +public class DiscIndexerPolicenauts extends DiscIndexer implements SectorClaimToPolicenauts.Listener { + + @Override + public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { + SectorClaimToPolicenauts sc = scs.getClaimer(SectorClaimToPolicenauts.class); + sc.setListener(this); + } + + @Override + public @CheckForNull DiscItem deserializeLineRead(@Nonnull SerializedDiscItem fields) throws LocalizedDeserializationFail { + if (DiscItemPolicenauts.TYPE_ID.equals(fields.getType())) + return new DiscItemPolicenauts(getCd(), fields); + return null; + } + + private class VidBuilder { + @Nonnull + private final Dimensions __dims; + private final int __iStartKlbsStartSector; + private int __iLastKlbsEndSector; + @CheckForNull + private IndexSectorFrameNumber.Format.Builder __indexSectorFrameNumberBuilder; + @CheckForNull + private HeaderFrameNumber.Format.Builder __headerFrameNumberBuilder; + private int __iSoundUnitCount = 0; + + public VidBuilder(@Nonnull SPacketData firstPacket, Dimensions dims) { + __dims = dims; + __iStartKlbsStartSector = firstPacket.getKlbsStartSectorNum(); + __iLastKlbsEndSector = firstPacket.getKlbsEndSectorNum(); + addPacket(firstPacket); + } + + final public void addPacket(@Nonnull SPacketData packet) { + __iLastKlbsEndSector = packet.getKlbsEndSectorNum(); + + if (packet.isAudio()) { + __iSoundUnitCount += packet.getSoundUnitCount(); + } else if (packet.isVideo()) { + if (__headerFrameNumberBuilder == null) + __headerFrameNumberBuilder = new HeaderFrameNumber.Format.Builder(packet.getTimestamp()); + else + __headerFrameNumberBuilder.addHeaderFrameNumber(packet.getTimestamp()); + + if (__indexSectorFrameNumberBuilder == null) + __indexSectorFrameNumberBuilder = new IndexSectorFrameNumber.Format.Builder(packet.getStartSector()); + else + __indexSectorFrameNumberBuilder.addFrameStartSector(packet.getStartSector()); + } + } + + public void finishVid(ILocalizedLogger log) { + if (__indexSectorFrameNumberBuilder == null) { + log.log(Level.WARNING, I.POLICENAUTS_DATA_CORRUPTION()); + return; + } + DiscItemPolicenauts di = new DiscItemPolicenauts(getCd(), + __iStartKlbsStartSector, __iLastKlbsEndSector, __dims, + __indexSectorFrameNumberBuilder.makeFormat(), + __headerFrameNumberBuilder.makeFormat(), + __iSoundUnitCount); + addDiscItem(di); + } + } + + @CheckForNull + private VidBuilder _currentVid; + @CheckForNull + private Dimensions _dims; + + @Override + public void videoStart(int iWidth, int iHeight, @Nonnull ILocalizedLogger log) { + if (_currentVid != null) { + _currentVid.finishVid(log); + _currentVid = null; + } + _dims = new Dimensions(iWidth, iHeight); + } + + @Override + public void feedPacket(@Nonnull SPacketData packet, @Nonnull ILocalizedLogger log) { + if (_dims == null) { + log.log(Level.WARNING, I.POLICENAUTS_DATA_CORRUPTION()); + return; + } + if (_currentVid == null) + _currentVid = new VidBuilder(packet, _dims); + else + _currentVid.addPacket(packet); + } + + @Override + public void endOfSectors(@Nonnull ILocalizedLogger log) { + if (_currentVid != null) { + _currentVid.finishVid(log); + _currentVid = null; + _dims = null; + } + } + + @Override + public void listPostProcessing(@Nonnull Collection allItems) { + } + + @Override + public void indexGenerated(@Nonnull DiscIndex index) { + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/DiscItemPolicenauts.java b/jpsxdec/src/jpsxdec/modules/policenauts/DiscItemPolicenauts.java new file mode 100644 index 0000000..6cc006c --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/DiscItemPolicenauts.java @@ -0,0 +1,260 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.sound.sampled.AudioFormat; +import jpsxdec.adpcm.SpuAdpcmDecoder; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.discitems.SerializedDiscItem; +import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.sharedaudio.DecodedAudioPacket; +import jpsxdec.modules.video.Dimensions; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.modules.video.framenumber.HeaderFrameNumber; +import jpsxdec.modules.video.framenumber.IFrameNumberFormatterWithHeader; +import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; +import jpsxdec.modules.video.packetbased.DiscItemPacketBasedVideoStream; +import jpsxdec.modules.video.packetbased.SectorClaimToAudioAndFrame; +import jpsxdec.util.Fraction; + +public class DiscItemPolicenauts extends DiscItemPacketBasedVideoStream { + + public static final String TYPE_ID = "Policenauts"; + + @Nonnull + private final HeaderFrameNumber.Format _timestampFrameNumberFormat; + + public DiscItemPolicenauts(@Nonnull CdFileSectorReader cd, + int iStartSector, int iEndSector, + @Nonnull Dimensions dim, + @Nonnull IndexSectorFrameNumber.Format sectorIndexFrameNumberFormat, + @Nonnull HeaderFrameNumber.Format timestampFrameNumberFormat, + int iSoundUnitCount) + { + super(cd, iStartSector, iEndSector, dim, sectorIndexFrameNumberFormat, iSoundUnitCount); + _timestampFrameNumberFormat = timestampFrameNumberFormat; + } + + public DiscItemPolicenauts(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscItem fields) + throws LocalizedDeserializationFail + { + super(cd, fields); + _timestampFrameNumberFormat = new HeaderFrameNumber.Format(fields); + } + + @Override + public @Nonnull SerializedDiscItem serialize() { + SerializedDiscItem serial = super.serialize(); + _timestampFrameNumberFormat.serialize(serial); + return serial; + } + + @Override + public @Nonnull String getSerializationTypeId() { + return TYPE_ID; + } + + @Override + public boolean hasIndependentBitstream() { + return false; + } + + @Override + public @Nonnull FrameNumber getStartFrame() { + return _timestampFrameNumberFormat.getStartFrame(_indexSectorFrameNumberFormat); + } + + @Override + public FrameNumber getEndFrame() { + return _timestampFrameNumberFormat.getEndFrame(_indexSectorFrameNumberFormat); + } + + @Override + public @Nonnull List getFrameNumberTypes() { + return Arrays.asList(FrameNumber.Type.Index, FrameNumber.Type.Header, FrameNumber.Type.Sector); + } + + @Override + protected double getPacketBasedFpsInterestingDescription() { + return SPacket.FRAMES_PER_SECOND.asDouble(); + } + + @Override + public @Nonnull Fraction getSectorsPerFrame() { + return SPacket.SECTORS150_PER_FRAME; + } + + @Override + public double getApproxDuration() { + return getFrameCount() / SPacket.FRAMES_PER_SECOND.asDouble(); + } + + @Override + public int getAudioSampleFramesPerSecond() { + return SPacket.AUDIO_SAMPLE_FRAMES_PER_SECOND; + } + + @Override + public @Nonnull SectorClaimToAudioAndFrame makeAudioVideoDemuxer(double dblVolume) { + return new Demuxer(getWidth(), getHeight(), getStartSector(), dblVolume, + _timestampFrameNumberFormat.makeFormatter(_indexSectorFrameNumberFormat)); + } + + public class Demuxer extends SectorClaimToAudioAndFrame + implements SectorClaimToPolicenauts.Listener + { + private final int _iWidth, _iHeight; + @Nonnull + private final IFrameNumberFormatterWithHeader _fnf; + private final int _iStartSector; + + @CheckForNull + private IDemuxedFrame.Listener _frameListener; + @CheckForNull + private DecodedAudioPacket.Listener _audioListener; + + @Nonnull + private final SpuAdpcmDecoder.Mono _audioDecoder; + private Fraction _zeroTimestampOffset = Fraction.ZERO; + private boolean _blnPrevTimestampWas0 = false; + private int _iPrevDuration = 0; + + private final ByteArrayOutputStream _pcmOut = new ByteArrayOutputStream(); + + public Demuxer(int iWidth, int iHeight, int iStartSector, double dblVolume, + @Nonnull IFrameNumberFormatterWithHeader fnf) + { + _iWidth = iWidth; + _iHeight = iHeight; + _iStartSector = iStartSector; + _audioDecoder = new SpuAdpcmDecoder.Mono(dblVolume); + _fnf = fnf; + } + + public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { + SectorClaimToPolicenauts s2cs = scs.getClaimer(SectorClaimToPolicenauts.class); + s2cs.setListener(this); + s2cs.setRangeLimit(getStartSector(), getEndSector()); + } + + public void videoStart(int iWidth, int iHeight, ILocalizedLogger log) { + // not important here + } + + @Override + public void feedPacket(@Nonnull SPacketData packet, @Nonnull ILocalizedLogger log) throws LoggedFailure { + + if (packet.isAudio()) { + if (_audioListener != null) { + _pcmOut.reset(); + // The audio timestamp is a pain + // The first 3 audio packets all start at 0 and are 156 long + // after that, the numbers add up correctly + if (_blnPrevTimestampWas0 && packet.getTimestamp() == 0) { + _zeroTimestampOffset = _zeroTimestampOffset.add(SPacket.SECTORS150_PER_TIMESTAMP.multiply(_iPrevDuration)); + } + //System.out.println(_zeroTimestampOffset + " -------- " + packet); + Fraction close = SPacket.SECTORS150_PER_TIMESTAMP.multiply(packet.getTimestamp()).add(_iStartSector).add(_zeroTimestampOffset); + _blnPrevTimestampWas0 = packet.getTimestamp() == 0; + _iPrevDuration = packet.getDuration(); + packet.decodeAudio(_audioDecoder, _pcmOut); + DecodedAudioPacket aup = new DecodedAudioPacket(0, SPacket.AUDIO_FORMAT, close, _pcmOut.toByteArray()); + _audioListener.audioPacketComplete(aup, log); + } + } else if (packet.isVideo()) { + FrameNumber fn = _fnf.next(packet.getStartSector(), packet.getTimestamp(), log); + if (_frameListener != null) { + _frameListener.frameComplete(new DemuxPolicenautsFrame(_iWidth, _iHeight, packet, fn, + SPacket.SECTORS150_PER_TIMESTAMP.multiply(packet.getTimestamp()).add(_iStartSector))); + } + } + } + + public void endOfSectors(ILocalizedLogger log) { + // not important here + } + + public void setFrameListener(@CheckForNull IDemuxedFrame.Listener listener) { + _frameListener = listener; + } + + public void setAudioListener(@Nonnull DecodedAudioPacket.Listener listener) { + _audioListener = listener; + } + + public @Nonnull AudioFormat getOutputFormat() { + return SPacket.AUDIO_FORMAT; + } + + public double getVolume() { + return _audioDecoder.getVolume(); + } + + public int getAbsolutePresentationStartSector() { + return DiscItemPolicenauts.this.getStartSector(); + } + + public int getStartSector() { + return DiscItemPolicenauts.this.getStartSector(); + } + + public int getEndSector() { + return DiscItemPolicenauts.this.getEndSector(); + } + + public int getSampleFramesPerSecond() { + return SPacket.AUDIO_SAMPLE_FRAMES_PER_SECOND; + } + + public int getDiscSpeed() { + return 2; + } + + } + + +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/KlbsStreamReader.java b/jpsxdec/src/jpsxdec/modules/policenauts/KlbsStreamReader.java new file mode 100644 index 0000000..44dfef2 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/KlbsStreamReader.java @@ -0,0 +1,149 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import jpsxdec.util.BinaryDataNotRecognized; +import jpsxdec.util.DemuxPushInputStream; +import jpsxdec.util.IO; + + +public class KlbsStreamReader { + + private static final int READ_HEADER = 1; + private static final int READ_PACKET = 2; + private static final int DONE = 3; + + private final int _iEntryCount; + private final int _iKlbsStartSector; + private final int _iKlbsEndSectorInclusive; + + @Nonnull + private final DemuxPushInputStream _sectorStream; + + private List _sPackets; + private int _iNextRequiredBytes = 0; + private int _iState = READ_HEADER; + private int _iPacketDataRead = 0; + + public KlbsStreamReader(@Nonnull SectorPN_KLBS klbsSector) { + _iEntryCount = klbsSector.getEntryCount(); + _iKlbsStartSector = klbsSector.getSectorNumber(); + _iKlbsEndSectorInclusive = klbsSector.getEndSectorInclusive(); + + _sectorStream = new DemuxPushInputStream(klbsSector); + + try { + IO.skip(_sectorStream, SectorPN_KLBS.SIZEOF_KLBS_HEADER); + } catch (IOException ex) { + throw new RuntimeException("Should not happen", ex); + } + + _iNextRequiredBytes = _iEntryCount * SPacket.SIZEOF; + } + + public void addSector(@Nonnull SectorPolicenauts sector) { + _sectorStream.addPiece(sector); + } + + public boolean allRead() { + return _iState == DONE; + } + + public @Nonnull List readAllAvailablePackets() throws BinaryDataNotRecognized { + try { + return doReadAllAvailablePackets(); + } catch (IOException ex) { + throw new RuntimeException("Should not happen", ex); + } + } + + private @Nonnull List doReadAllAvailablePackets() throws BinaryDataNotRecognized, IOException { + + List finishedPackets = null; + + while (_sectorStream.available() >= _iNextRequiredBytes && _iState != DONE) { + switch (_iState) { + case READ_HEADER: + _sPackets = SPacketPos.readPackets(_sectorStream, _iEntryCount, _iKlbsStartSector, _iKlbsEndSectorInclusive); + _iNextRequiredBytes = _sPackets.get(0).getSize(); + _iState = READ_PACKET; + break; + + case READ_PACKET: + SPacketPos packetPos = _sPackets.get(_iPacketDataRead); + skipZeroes(packetPos.getPaddingBeforeThisPacket()); + + _sectorStream.mark(packetPos.getSize()); + SPacketData packetData = packetPos.read(_sectorStream); + if (finishedPackets == null) + finishedPackets = new ArrayList(3); + finishedPackets.add(packetData); + + _iPacketDataRead++; + if (_iPacketDataRead >= _sPackets.size()) { + _iState = DONE; + } else { + SPacketPos nextPacket = _sPackets.get(_iPacketDataRead); + _iNextRequiredBytes = nextPacket.getPaddingBeforeThisPacket() + nextPacket.getSize(); + } + break; + } + } + + if (finishedPackets == null) + finishedPackets = Collections.emptyList(); + return finishedPackets; + } + + private void skipZeroes(int iCount) throws IOException, BinaryDataNotRecognized { + while (iCount > 0) { + int iPos = _sectorStream.getOffsetInCurrentPiece(); + int iByte = _sectorStream.read(); + if (iByte != 0) { + throw new BinaryDataNotRecognized("Expected 0 at " + iPos + " in sector " + _sectorStream.getCurrentPiece() +" but got " + iByte); + } + iCount--; + } + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SPacket.java b/jpsxdec/src/jpsxdec/modules/policenauts/SPacket.java new file mode 100644 index 0000000..e7c62e3 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SPacket.java @@ -0,0 +1,166 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.sound.sampled.AudioFormat; +import jpsxdec.adpcm.SoundUnitDecoder; +import jpsxdec.adpcm.SpuAdpcmSoundUnit; +import jpsxdec.util.BinaryDataNotRecognized; +import jpsxdec.util.Fraction; +import jpsxdec.util.IO; +import jpsxdec.util.Misc; + +/** Policenauts FMVs consist of several packets of data. + * All the packets have a type identifier, and all the identifiers start with + * the letter 'S'. */ +public class SPacket { + + public static final int AUDIO_SAMPLE_FRAMES_PER_SECOND = 44100; // confirmed + public static final AudioFormat AUDIO_FORMAT = new AudioFormat(AUDIO_SAMPLE_FRAMES_PER_SECOND, 16, 1, true, false); + public static final Fraction AUDIO_SAMPLE_FRAMES_PER_TIMESTAMP = + new Fraction(16384 / SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT * SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT, 156); + public static final Fraction TIMESTAMP_UNITS_PER_SECOND = Fraction.divide(AUDIO_SAMPLE_FRAMES_PER_SECOND, AUDIO_SAMPLE_FRAMES_PER_TIMESTAMP); + public static final Fraction SECONDS_PER_TIMESTAMP = Fraction.divide(1, TIMESTAMP_UNITS_PER_SECOND); + + public static final int TIMESTAMP_UNITS_PER_FRAME = 20; + public static final Fraction FRAMES_PER_SECOND = TIMESTAMP_UNITS_PER_SECOND.divide(TIMESTAMP_UNITS_PER_FRAME); + + public static final Fraction SECTORS150_PER_FRAME = Fraction.divide(150, FRAMES_PER_SECOND); + public static final Fraction SECTORS150_PER_TIMESTAMP = SECONDS_PER_TIMESTAMP.multiply(150); + + public enum Type { + /** SPU ADPCM. */ + SDNSSDTS("SDNSSDTS (audio)"), + /** Frame bitstream. */ + SCIPPDTS("SCIPPDTS (video)"), + SDNSHDTS, + SCTELLEC, + SCTEGOLD, + SCTEGLEC, + SCTEMLEC; + + @CheckForNull + private final String _sToString; + + private Type() { _sToString = null; } + private Type(String sToString) { _sToString = sToString; } + + @Override + public String toString() { + return _sToString == null ? super.toString() : _sToString; + } + } + + public static final int SIZEOF = 48; + + // Zeroes // 8 bytes @ 0 + @Nonnull + private final Type _type; // 8 bytes @ 8 + private final int _iTimestamp; // 4 bytes @ 16 + private final int _iDuration; // 4 bytes @ 20 + private final int _iOffset; // 4 bytes @ 24 + private final int _iSize; // 4 bytes @ 28 + // Zeroes // 16 bytes @ 32 + + public SPacket(@Nonnull InputStream is) throws EOFException, IOException, BinaryDataNotRecognized { + + long lng8Zeroes = IO.readSInt64BE(is); + if (lng8Zeroes != 0) + throw new BinaryDataNotRecognized(); + + byte[] abTag = new byte[8]; + IO.readByteArray(is, abTag); + try { + _type = Type.valueOf(Misc.asciiToString(abTag)); + } catch (IllegalArgumentException ex) { + throw new BinaryDataNotRecognized(ex); + } + _iTimestamp = IO.readSInt32LE(is); + if (_iTimestamp < 0 || _iTimestamp > 63780) + throw new BinaryDataNotRecognized(); + _iDuration = IO.readSInt32LE(is); + if (_iDuration < 0 || _iDuration > 156) + throw new BinaryDataNotRecognized(); + _iOffset = IO.readSInt32LE(is); + if (_iOffset < 1) + throw new BinaryDataNotRecognized(); + _iSize = IO.readSInt32LE(is); + if (_iSize < 5 || _iSize > 17000) + throw new BinaryDataNotRecognized(); + lng8Zeroes = IO.readSInt64BE(is); + if (lng8Zeroes != 0) + throw new BinaryDataNotRecognized(); + lng8Zeroes = IO.readSInt64BE(is); + if (lng8Zeroes != 0) + throw new BinaryDataNotRecognized(); + + if (_type == Type.SDNSSDTS && !(_iSize % SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT == 0)) + throw new BinaryDataNotRecognized(); + } + + public @Nonnull Type getType() { + return _type; + } + + public int getTimestamp() { + return _iTimestamp; + } + + public int getDuration() { + return _iDuration; + } + + public int getOffset() { + return _iOffset; + } + + public int getSize() { + return _iSize; + } + + @Override + public String toString() { + return String.format("%s Time:%d Duration:%d Offset:%d Size:%d", + _type, _iTimestamp, _iDuration, _iOffset, _iSize); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SPacketData.java b/jpsxdec/src/jpsxdec/modules/policenauts/SPacketData.java new file mode 100644 index 0000000..db94afd --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SPacketData.java @@ -0,0 +1,113 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import javax.annotation.Nonnull; +import jpsxdec.adpcm.SpuAdpcmDecoder; +import jpsxdec.adpcm.SpuAdpcmSoundUnit; + + +public class SPacketData { + + @Nonnull + private final SPacketPos _sPacketPos; + @Nonnull + private final byte[] _abData; + + public SPacketData(@Nonnull SPacketPos sPacketPos, @Nonnull byte[] abData) { + _sPacketPos = sPacketPos; + _abData = abData; + } + + public boolean isAudio() { + return _sPacketPos.isAudio(); + } + + public boolean isVideo() { + return _sPacketPos.isVideo(); + } + + public @Nonnull byte[] getData() { + return _abData; + } + + public int getSoundUnitCount() { + return _abData.length / SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT; + } + + public void decodeAudio(@Nonnull SpuAdpcmDecoder.Mono decoder, @Nonnull OutputStream pcmOut) { + ByteArrayInputStream spuIn = new ByteArrayInputStream(_abData); + try { + decoder.decode(spuIn, getSoundUnitCount(), pcmOut); + } catch (IOException ex) { + throw new RuntimeException("Should not happen", ex); + } + } + + public int getKlbsStartSectorNum() { + return _sPacketPos.getKlbsStartSectorNum(); + } + + public int getKlbsEndSectorNum() { + return _sPacketPos.getKlbsEndSectorNum(); + } + + public int getStartSector() { + return _sPacketPos.getStartSector(); + } + + public int getEndSector() { + return _sPacketPos.getEndSector(); + } + + public int getTimestamp() { + return _sPacketPos.getTimestamp(); + } + + public int getDuration() { + return _sPacketPos.getDuration(); + } + + @Override + public String toString() { + return _sPacketPos.toString(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SPacketPos.java b/jpsxdec/src/jpsxdec/modules/policenauts/SPacketPos.java new file mode 100644 index 0000000..102bf71 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SPacketPos.java @@ -0,0 +1,195 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import jpsxdec.adpcm.SpuAdpcmSoundUnit; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.util.BinaryDataNotRecognized; +import jpsxdec.util.IO; + +/** Wraps a {@link SPacket} with information about its position of the data + * on the disk. */ +public class SPacketPos { + + private static final Logger LOG = Logger.getLogger(SPacketPos.class.getName()); + + + public static @Nonnull List readPackets(@Nonnull InputStream is, int iEntryCount, + int iKlbsStartSector, int iKlbsEndSectorInclusive) + throws IOException, BinaryDataNotRecognized + { + ArrayList _sPackets = new ArrayList(); + for (int i = 0; i < iEntryCount; i++) { + SPacket packet = new SPacket(is); + + SPacketPos packetPos = new SPacketPos(packet, iKlbsStartSector, iKlbsEndSectorInclusive, i); + if (i > 0) { + SPacketPos prev = _sPackets.get(i - 1); + prev.checkAgainstNextPacket(packetPos); + } + _sPackets.add(packetPos); + } + return _sPackets; + } + + + @Nonnull + private final SPacket _sPacket; + + private final int _iKlbsStartSectorNum; + private final int _iKlbsEndSectorNumInclusive; + private final int _iIndexInKlbs; + private final int _iStartSector; + private final int _iStartOfs; + + /** 1 FMV has an incorrect size, so we'll correct it later. */ + private int _iCorrectedSize; + + private int _iPaddingBeforeThisPacket = 0; + private int _iPaddingAfterThisPacket = 0; + + public SPacketPos(@Nonnull SPacket sPacket, int iKlbsStartSectorNum, int iKlbsEndSectorNumInclusive, int iIndexInKlbs) { + _sPacket = sPacket; + _iKlbsStartSectorNum = iKlbsStartSectorNum; + _iKlbsEndSectorNumInclusive = iKlbsEndSectorNumInclusive; + _iIndexInKlbs = iIndexInKlbs; + _iStartSector = iKlbsStartSectorNum + (sPacket.getOffset() / CdSector.SECTOR_SIZE_2048_ISO); + _iStartOfs = sPacket.getOffset() % CdSector.SECTOR_SIZE_2048_ISO; + + _iCorrectedSize = sPacket.getSize(); + } + + public int getSize() { + return _iCorrectedSize; + } + + public int getPaddingBeforeThisPacket() { + return _iPaddingBeforeThisPacket; + } + + public int getTimestamp() { + return _sPacket.getTimestamp(); + } + + public int getDuration() { + return _sPacket.getDuration(); + } + + public boolean isVideo() { + return _sPacket.getType() == SPacket.Type.SCIPPDTS; + } + + public boolean isAudio() { + return _sPacket.getType() == SPacket.Type.SDNSSDTS; + } + + public int getKlbsStartSectorNum() { + return _iKlbsStartSectorNum; + } + + public int getKlbsEndSectorNum() { + return _iKlbsEndSectorNumInclusive; + } + + public void checkAgainstNextPacket(@Nonnull SPacketPos nextPacket) throws BinaryDataNotRecognized { + if (nextPacket.getPacket().getOffset() < _sPacket.getOffset()) + throw new BinaryDataNotRecognized(); + + // Some timestamps can be a little before the next one + if (nextPacket.getPacket().getTimestamp() < _sPacket.getTimestamp()) { + LOG.log(Level.INFO, "{0} < {1}", new Object[]{nextPacket, _sPacket}); + if (nextPacket.getPacket().getTimestamp() + 15 < _sPacket.getTimestamp()) { + throw new BinaryDataNotRecognized("Next packet timestamp " + nextPacket.getPacket().getTimestamp() + + " < current packet timestamp " + _sPacket.getTimestamp()); + } + } + + int iBytesBetweenPacketStarts = nextPacket._sPacket.getOffset() - _sPacket.getOffset(); + if (_sPacket.getSize() > iBytesBetweenPacketStarts) { + LOG.log(Level.WARNING, "{0} packet size exceeds room {1,number,#} (expected for MV000003.MOV on disc 2)", + new Object[]{this, iBytesBetweenPacketStarts}); + if (isAudio() && !(iBytesBetweenPacketStarts % SpuAdpcmSoundUnit.SIZEOF_SOUND_UNIT == 0)) + throw new BinaryDataNotRecognized(); + _iCorrectedSize = iBytesBetweenPacketStarts; + } + _iPaddingAfterThisPacket = iBytesBetweenPacketStarts - _iCorrectedSize; + nextPacket._iPaddingBeforeThisPacket = _iPaddingAfterThisPacket; + } + + /** Reads the packet data and returns a {@link SPacketData}. */ + public @Nonnull SPacketData read(@Nonnull InputStream is) throws EOFException, IOException { + byte[] abData = IO.readByteArray(is, _iCorrectedSize); + return new SPacketData(this, abData); + } + + public int getStartSector() { + return _iStartSector; + } + + public int getEndSector() { + return _iStartSector + (_sPacket.getOffset() + _iCorrectedSize) / CdSector.SECTOR_SIZE_2048_ISO; + } + + /** The packet data is padded to a 4 byte boundary, so there is often + * a few bytes between packets */ + public int bytesToThisPacket(int iFromSector, int iFromOffset) { + int iSectorDiff = _iStartSector - iFromSector; + int iOfsDiff = _iStartOfs - iFromOffset; + int i = iSectorDiff * CdSector.SECTOR_SIZE_2048_ISO + iOfsDiff; + return i; + } + + public @Nonnull SPacket getPacket() { + return _sPacket; + } + + @Override + public String toString() { + return String.format("Start %d [%d] sector.ofs %d.%d %s", + _iKlbsStartSectorNum, _iIndexInKlbs, _iStartSector, _iStartOfs, _sPacket); + } + +} \ No newline at end of file diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SectorClaimToPolicenauts.java b/jpsxdec/src/jpsxdec/modules/policenauts/SectorClaimToPolicenauts.java new file mode 100644 index 0000000..ca8dbb9 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SectorClaimToPolicenauts.java @@ -0,0 +1,186 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.i18n.I; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.util.BinaryDataNotRecognized; +import jpsxdec.util.IOIterator; + + +public class SectorClaimToPolicenauts extends SectorClaimSystem.SectorClaimer { + + private static final Logger LOG = Logger.getLogger(SectorClaimToPolicenauts.class.getName()); + + public interface Listener { + void videoStart(int iWidth, int iHeight, @Nonnull ILocalizedLogger log); + void feedPacket(@Nonnull SPacketData packet, @Nonnull ILocalizedLogger log) + throws LoggedFailure; + public void endOfSectors(@Nonnull ILocalizedLogger log); + } + + + private static class KlbsSectorRange { + /** Stream to read the packet data in this KLBS set of sectors. + * Once all are read, the stream ends, but we want to stick around to + * collect any more sectors in this KLBS. */ + @CheckForNull + private KlbsStreamReader _stream; + private final int _iKlbsEndSectorInclusive; + private SectorPN_KLBS _klbsSector; + private boolean _blnAtEnd = false; + + public KlbsSectorRange(@Nonnull SectorPN_KLBS klbsSector) { + _klbsSector = klbsSector; + _stream = new KlbsStreamReader(klbsSector); + _iKlbsEndSectorInclusive = klbsSector.getEndSectorInclusive(); + } + + public @Nonnull SectorPN_KLBS start(@Nonnull ILocalizedLogger log) { + SectorPN_KLBS klbsSector = _klbsSector; + _klbsSector = null; // free up the KLBS sector memory and underlying buffer + doRead(klbsSector, log); + return klbsSector; + } + + public @Nonnull SectorPolicenauts addSector(@Nonnull CdSector cdSector, @Nonnull ILocalizedLogger log) { + _blnAtEnd = cdSector.getSectorIndexFromStart() >= _iKlbsEndSectorInclusive; + SectorPolicenauts pnSector = new SectorPolicenauts(cdSector, _blnAtEnd); + if (_stream != null) { + _stream.addSector(pnSector); + doRead(pnSector, log); + } + return pnSector; + } + + public boolean atEnd() { + return _blnAtEnd; + } + + private void doRead(SectorPolicenauts pnSector, @Nonnull ILocalizedLogger log) { + try { + List finishedPackets = _stream.readAllAvailablePackets(); + if (_stream.allRead()) + _stream = null; + pnSector.setPacketsEndingInThisSector(finishedPackets); + } catch (BinaryDataNotRecognized ex) { + log.log(Level.SEVERE, I.POLICENAUTS_DATA_CORRUPTION(), ex); + _stream = null; + } + } + } + + + @CheckForNull + private Listener _listener; + @CheckForNull + private KlbsSectorRange _currentKlbs; + + + public void setListener(@CheckForNull Listener listener) { + _listener = listener; + } + + @Override + public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, + @Nonnull IOIterator peekIt, + @Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + if (cs.isClaimed()) { + checkCorruptionIfExistingKlbs(log); + return; + } + + SectorPN_VMNK vmnkSector = new SectorPN_VMNK(cs.getSector()); + if (vmnkSector.getProbability() == 100) { + checkCorruptionIfExistingKlbs(log); + cs.claim(vmnkSector); + if (_listener != null) + _listener.videoStart(vmnkSector.getWidth(), vmnkSector.getHeight(), log); + } else { + SectorPolicenauts pnSector = null; + SectorPN_KLBS klbsSector = new SectorPN_KLBS(cs.getSector()); + if (klbsSector.getProbability() == 100) { + checkCorruptionIfExistingKlbs(log); + _currentKlbs = new KlbsSectorRange(klbsSector); + klbsSector = _currentKlbs.start(log); + cs.claim(klbsSector); + pnSector = klbsSector; + } else if (_currentKlbs != null) { + pnSector = _currentKlbs.addSector(cs.getSector(), log); + cs.claim(pnSector); + if (_currentKlbs.atEnd()) + _currentKlbs = null; + } + + if (_listener != null && pnSector != null) { + for (SPacketData sPacketData : pnSector) { + try { + _listener.feedPacket(sPacketData, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } + } + } + } + + /** There should not already be an existing KLBS block we are tracking. */ + private void checkCorruptionIfExistingKlbs(@Nonnull ILocalizedLogger log) { + if (_currentKlbs != null) { + log.log(Level.WARNING, I.POLICENAUTS_DATA_CORRUPTION()); + _currentKlbs = null; + } + } + + @Override + public void endOfSectors(@Nonnull ILocalizedLogger log) { + if (_listener != null) + _listener.endOfSectors(log); + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_KLBS.java b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_KLBS.java new file mode 100644 index 0000000..c2e1a7b --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_KLBS.java @@ -0,0 +1,103 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.util.Misc; + +public class SectorPN_KLBS extends SectorPolicenauts { + + public static final int SIZEOF_KLBS_HEADER = 32; + public static final int KLBS_SECTOR_COUNT = 128; + + // Zeroes // 8 bytes @0 + // "KLBS" // 4 bytes @8 + private int _iSize; // 4 bytes @12 always = 262144 = 128 * 2048 + private int _iEntryCount; // 4 bytes @16 + // Entry count again // 4 bytes @20 + // Zeroes // 8 bytes @24 + + public SectorPN_KLBS(@Nonnull CdSector cdSector) { + super(cdSector, false); + if (isSuperInvalidElseReset()) return; + + for (int i = 0; i < 8; i++) { + if (cdSector.readUserDataByte(i) != 0) return; + } + + byte[] abKlbs = new byte[4]; + cdSector.getCdUserDataCopy(8, abKlbs, 0, 4); + String sKlbs = Misc.asciiToString(abKlbs); + if (!"KLBS".equals(sKlbs)) return; + + _iSize = cdSector.readSInt32LE(12); + if (_iSize != KLBS_SECTOR_COUNT * CdSector.SECTOR_SIZE_2048_ISO /*262144*/) return; + _iEntryCount = cdSector.readSInt32LE(16); + if (_iEntryCount < 1 || _iEntryCount > 83) return; + int iEntryCount2 = cdSector.readSInt32LE(20); + if (iEntryCount2 != _iEntryCount) return; + for (int i = 24; i < SIZEOF_KLBS_HEADER; i++) { + if (cdSector.readUserDataByte(i) != 0) return; + } + + setProbability(100); + } + + public int getSectorLength() { + return KLBS_SECTOR_COUNT; + } + + public int getEndSectorInclusive() { + return getSectorNumber() + KLBS_SECTOR_COUNT - 1; + } + + public int getEntryCount() { + return _iEntryCount; + } + + @Override + public String getTypeName() { + return "Policenauts KLBS"; + } + + @Override + public String toString() { + return String.format("%s %s size:%d entries:%d", getTypeName(), super.toString(), _iSize, _iEntryCount); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_VMNK.java b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_VMNK.java new file mode 100644 index 0000000..1003135 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPN_VMNK.java @@ -0,0 +1,104 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.modules.IdentifiedSector; + +public class SectorPN_VMNK extends IdentifiedSector { + + public static final int WIDTH = 288; + public static final int HEIGHT = 144; + + // I don't know what these mean + private static final byte[] VMNK_HEADER = { + 'V', 'M', 'N', 'K', + (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0xF0, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x04, (byte)0x00, + (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00 + }; + + private int _iWidth; + private int _iHeight; + + public SectorPN_VMNK(@Nonnull CdSector cdSector) { + super(cdSector); + if (isSuperInvalidElseReset()) return; + + for (int i = 0; i < VMNK_HEADER.length; i++) { + if (cdSector.readUserDataByte(i) != VMNK_HEADER[i]) + return; + } + + _iWidth = cdSector.readSInt32LE(28); + if (_iWidth != WIDTH) + return; + _iHeight = cdSector.readSInt32LE(32); + if (_iHeight != HEIGHT) + return; + + for (int i = VMNK_HEADER.length + 8; i < cdSector.getCdUserDataSize(); i++) { + if (cdSector.readUserDataByte(i) != 0) + return; + } + + setProbability(100); + } + + public @Nonnull String getTypeName() { + return "Policenauts VMNK"; + } + + public int getWidth() { + return _iWidth; + } + + public int getHeight() { + return _iHeight; + } + + @Override + public String toString() { + return String.format("%s %s %dx%d", getTypeName(), super.toString(), _iWidth, _iHeight); + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/policenauts/SectorPolicenauts.java b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPolicenauts.java new file mode 100644 index 0000000..d5ab3f9 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/policenauts/SectorPolicenauts.java @@ -0,0 +1,108 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.policenauts; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.modules.IdentifiedSector; +import jpsxdec.util.DemuxedData; + +/** Just a simple sector wrapper for Policenauts to claim sectors. */ +public class SectorPolicenauts extends IdentifiedSector implements Iterable, DemuxedData.Piece { + + private boolean _blnIsEnd; + + @Nonnull + private List _packetsEndingInThisSector = Collections.emptyList(); + + public SectorPolicenauts(@Nonnull CdSector cdSector, boolean blnIsEnd) { + super(cdSector); + if (isSuperInvalidElseReset()) return; + _blnIsEnd = blnIsEnd; + setProbability(100); + } + + @Override + public String getTypeName() { + return "Policenauts"; + } + + void setPacketsEndingInThisSector(@Nonnull List packetsEndingInThisSector) { + _packetsEndingInThisSector = packetsEndingInThisSector; + } + + @Override + public int getDemuxPieceSize() { + return getCdSector().getCdUserDataSize(); + } + + @Override + public byte getDemuxPieceByte(int i) { + return getCdSector().readUserDataByte(i); + } + + @Override + public void copyDemuxPieceData(@Nonnull byte[] abOut, int iOutPos) { + getCdSector().getCdUserDataCopy(0, abOut, iOutPos, getDemuxPieceSize()); + } + + @Override + public @Nonnull Iterator iterator() { + return _packetsEndingInThisSector.iterator(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getTypeName()).append(' ') + .append(super.toString()).append(' ') + .append(_packetsEndingInThisSector.size()).append(" finished packets ["); + for (int i = 0; i < _packetsEndingInThisSector.size(); i++) { + if (i > 0) + sb.append(", "); + sb.append(_packetsEndingInThisSector.get(i)); + } + sb.append(']'); + if (_blnIsEnd) + sb.append(" END"); + return sb.toString(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/BitStreamUncompressorRoadRash.java b/jpsxdec/src/jpsxdec/modules/roadrash/BitStreamUncompressorRoadRash.java new file mode 100644 index 0000000..697afea --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/BitStreamUncompressorRoadRash.java @@ -0,0 +1,319 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.bitstreams.ArrayBitReader; +import jpsxdec.psxvideo.bitstreams.BitStreamCode; +import jpsxdec.psxvideo.bitstreams.BitStreamCompressor; +import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; +import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; +import jpsxdec.psxvideo.bitstreams.ZeroRunLengthAcLookup; + +public class BitStreamUncompressorRoadRash extends BitStreamUncompressor { + + private final int _iQuantizationScale; + + public BitStreamUncompressorRoadRash(@Nonnull byte[] abMdecPacketPayload, + @Nonnull ZeroRunLengthAcLookup lookupTable, + int iQuantizationScale) + { + super( + new ArrayBitReader(abMdecPacketPayload, abMdecPacketPayload.length, true), + lookupTable, + new BitStreamUncompressor_STRv2.QuantizationDc_STRv12(iQuantizationScale), + BitStreamUncompressor_STRv2.AC_ESCAPE_CODE_STR, + BitStreamUncompressor.FRAME_END_PADDING_BITS_NONE + ); + _iQuantizationScale = iQuantizationScale; + } + + @Override + public @Nonnull BitStreamCompressor makeCompressor() { + // Writing a Road Rashvideo encoder would be a significant amount of work. + // And that might be ok, except to properly replace Road Rash videos would + // require re-building all of the packets from start to finish. + // That is beyond the scope jPSXdec's functionality. Modders will have + // to handle that part on their own. + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return super.toString() + " qscale:" + _iQuantizationScale; + } + + /** + * This MDEC code has special mean, it is treated as the escape code. + */ + static final int BITSTREAM_ESCAPE_CODE = 0x7c1f; + + /** The VLC0 packet contains a list of MDEC codes that are mapped to bit-codes in this order. */ + static final BitStreamCode[] ROAD_RASH_BIT_CODE_ORDER = { + BitStreamCode._10_______________, + BitStreamCode._110______________, + BitStreamCode._111______________, + BitStreamCode._0110_____________, + BitStreamCode._0111_____________, + BitStreamCode._01000____________, + BitStreamCode._01001____________, + BitStreamCode._01010____________, + BitStreamCode._01011____________, + BitStreamCode._001010___________, + BitStreamCode._001011___________, + BitStreamCode._001110___________, + BitStreamCode._001111___________, + BitStreamCode._001100___________, + BitStreamCode._001101___________, + BitStreamCode._0001100__________, + BitStreamCode._0001101__________, + BitStreamCode._0001110__________, + BitStreamCode._0001111__________, + BitStreamCode._0001010__________, + BitStreamCode._0001011__________, + BitStreamCode._0001000__________, + BitStreamCode._0001001__________, + BitStreamCode._000001___________, + BitStreamCode._00001100_________, + BitStreamCode._00001101_________, + BitStreamCode._00001000_________, + BitStreamCode._00001001_________, + BitStreamCode._00001110_________, + BitStreamCode._00001111_________, + BitStreamCode._00001010_________, + BitStreamCode._00001011_________, + BitStreamCode._001001100________, + BitStreamCode._001001101________, + BitStreamCode._001000010________, + BitStreamCode._001000011________, + BitStreamCode._001001010________, + BitStreamCode._001001011________, + BitStreamCode._001001000________, + BitStreamCode._001001001________, + BitStreamCode._001001110________, + BitStreamCode._001001111________, + BitStreamCode._001000110________, + BitStreamCode._001000111________, + BitStreamCode._001000100________, + BitStreamCode._001000101________, + BitStreamCode._001000000________, + BitStreamCode._001000001________, + BitStreamCode._00000010100______, + BitStreamCode._00000010101______, + BitStreamCode._00000011000______, + BitStreamCode._00000011001______, + BitStreamCode._00000010110______, + BitStreamCode._00000010111______, + BitStreamCode._00000011110______, + BitStreamCode._00000011111______, + BitStreamCode._00000010010______, + BitStreamCode._00000010011______, + BitStreamCode._00000011100______, + BitStreamCode._00000011101______, + BitStreamCode._00000011010______, + BitStreamCode._00000011011______, + BitStreamCode._00000010000______, + BitStreamCode._00000010001______, + BitStreamCode._0000000111010____, + BitStreamCode._0000000111011____, + BitStreamCode._0000000110000____, + BitStreamCode._0000000110001____, + BitStreamCode._0000000100110____, + BitStreamCode._0000000100111____, + BitStreamCode._0000000100000____, + BitStreamCode._0000000100001____, + BitStreamCode._0000000110110____, + BitStreamCode._0000000110111____, + BitStreamCode._0000000101000____, + BitStreamCode._0000000101001____, + BitStreamCode._0000000111000____, + BitStreamCode._0000000111001____, + BitStreamCode._0000000100100____, + BitStreamCode._0000000100101____, + BitStreamCode._0000000111100____, + BitStreamCode._0000000111101____, + BitStreamCode._0000000101010____, + BitStreamCode._0000000101011____, + BitStreamCode._0000000100010____, + BitStreamCode._0000000100011____, + BitStreamCode._0000000111110____, + BitStreamCode._0000000111111____, + BitStreamCode._0000000110100____, + BitStreamCode._0000000110101____, + BitStreamCode._0000000110010____, + BitStreamCode._0000000110011____, + BitStreamCode._0000000101110____, + BitStreamCode._0000000101111____, + BitStreamCode._0000000101100____, + BitStreamCode._0000000101101____, + BitStreamCode._00000000110100___, + BitStreamCode._00000000110101___, + BitStreamCode._00000000110010___, + BitStreamCode._00000000110011___, + BitStreamCode._00000000110000___, + BitStreamCode._00000000110001___, + BitStreamCode._00000000101110___, + BitStreamCode._00000000101111___, + BitStreamCode._00000000101100___, + BitStreamCode._00000000101101___, + BitStreamCode._00000000101010___, + BitStreamCode._00000000101011___, + BitStreamCode._00000000101000___, + BitStreamCode._00000000101001___, + BitStreamCode._00000000100110___, + BitStreamCode._00000000100111___, + BitStreamCode._00000000100100___, + BitStreamCode._00000000100101___, + BitStreamCode._00000000100010___, + BitStreamCode._00000000100011___, + BitStreamCode._00000000100000___, + BitStreamCode._00000000100001___, + BitStreamCode._00000000111110___, + BitStreamCode._00000000111111___, + BitStreamCode._00000000111100___, + BitStreamCode._00000000111101___, + BitStreamCode._00000000111010___, + BitStreamCode._00000000111011___, + BitStreamCode._00000000111000___, + BitStreamCode._00000000111001___, + BitStreamCode._00000000110110___, + BitStreamCode._00000000110111___, + BitStreamCode._000000000111110__, + BitStreamCode._000000000111111__, + BitStreamCode._000000000111100__, + BitStreamCode._000000000111101__, + BitStreamCode._000000000111010__, + BitStreamCode._000000000111011__, + BitStreamCode._000000000111000__, + BitStreamCode._000000000111001__, + BitStreamCode._000000000110110__, + BitStreamCode._000000000110111__, + BitStreamCode._000000000110100__, + BitStreamCode._000000000110101__, + BitStreamCode._000000000110010__, + BitStreamCode._000000000110011__, + BitStreamCode._000000000110000__, + BitStreamCode._000000000110001__, + BitStreamCode._000000000101110__, + BitStreamCode._000000000101111__, + BitStreamCode._000000000101100__, + BitStreamCode._000000000101101__, + BitStreamCode._000000000101010__, + BitStreamCode._000000000101011__, + BitStreamCode._000000000101000__, + BitStreamCode._000000000101001__, + BitStreamCode._000000000100110__, + BitStreamCode._000000000100111__, + BitStreamCode._000000000100100__, + BitStreamCode._000000000100101__, + BitStreamCode._000000000100010__, + BitStreamCode._000000000100011__, + BitStreamCode._000000000100000__, + BitStreamCode._000000000100001__, + BitStreamCode._0000000000110000_, + BitStreamCode._0000000000110001_, + BitStreamCode._0000000000101110_, + BitStreamCode._0000000000101111_, + BitStreamCode._0000000000101100_, + BitStreamCode._0000000000101101_, + BitStreamCode._0000000000101010_, + BitStreamCode._0000000000101011_, + BitStreamCode._0000000000101000_, + BitStreamCode._0000000000101001_, + BitStreamCode._0000000000100110_, + BitStreamCode._0000000000100111_, + BitStreamCode._0000000000100100_, + BitStreamCode._0000000000100101_, + BitStreamCode._0000000000100010_, + BitStreamCode._0000000000100011_, + BitStreamCode._0000000000100000_, + BitStreamCode._0000000000100001_, + BitStreamCode._0000000000111110_, + BitStreamCode._0000000000111111_, + BitStreamCode._0000000000111100_, + BitStreamCode._0000000000111101_, + BitStreamCode._0000000000111010_, + BitStreamCode._0000000000111011_, + BitStreamCode._0000000000111000_, + BitStreamCode._0000000000111001_, + BitStreamCode._0000000000110110_, + BitStreamCode._0000000000110111_, + BitStreamCode._0000000000110100_, + BitStreamCode._0000000000110101_, + BitStreamCode._0000000000110010_, + BitStreamCode._0000000000110011_, + BitStreamCode._00000000000100110, + BitStreamCode._00000000000100111, + BitStreamCode._00000000000100100, + BitStreamCode._00000000000100101, + BitStreamCode._00000000000100010, + BitStreamCode._00000000000100011, + BitStreamCode._00000000000100000, + BitStreamCode._00000000000100001, + BitStreamCode._00000000000101000, + BitStreamCode._00000000000101001, + BitStreamCode._00000000000110100, + BitStreamCode._00000000000110101, + BitStreamCode._00000000000110010, + BitStreamCode._00000000000110011, + BitStreamCode._00000000000110000, + BitStreamCode._00000000000110001, + BitStreamCode._00000000000101110, + BitStreamCode._00000000000101111, + BitStreamCode._00000000000101100, + BitStreamCode._00000000000101101, + BitStreamCode._00000000000101010, + BitStreamCode._00000000000101011, + BitStreamCode._00000000000111110, + BitStreamCode._00000000000111111, + BitStreamCode._00000000000111100, + BitStreamCode._00000000000111101, + BitStreamCode._00000000000111010, + BitStreamCode._00000000000111011, + BitStreamCode._00000000000111000, + BitStreamCode._00000000000111001, + BitStreamCode._00000000000110110, + BitStreamCode._00000000000110111, + }; + + static { + if (ROAD_RASH_BIT_CODE_ORDER.length != BitStreamCode.getTotalCount()) + throw new AssertionError("Road Rash VLC code count is wrong"); + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/DemuxedRoadRashFrame.java b/jpsxdec/src/jpsxdec/modules/roadrash/DemuxedRoadRashFrame.java new file mode 100644 index 0000000..7cbd694 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/DemuxedRoadRashFrame.java @@ -0,0 +1,124 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.io.PrintStream; +import java.util.Arrays; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; +import jpsxdec.util.Fraction; + + +public class DemuxedRoadRashFrame implements IDemuxedFrame { + + @Nonnull + private final RoadRashPacketSectors _sectors; + @Nonnull + private final RoadRashPacket.MDEC _mdecPacket; + @Nonnull + private final RoadRashPacket.VLC0 _vlc; + @Nonnull + private final FrameNumber _frameNumber; + @Nonnull + private final Fraction _presentationSector; + + public DemuxedRoadRashFrame(@Nonnull RoadRashPacketSectors sectors, + @Nonnull RoadRashPacket.MDEC mdecPacket, + @Nonnull RoadRashPacket.VLC0 vlc, + @Nonnull FrameNumber frameNumber, + @Nonnull Fraction presentationSector) + { + _sectors = sectors; + _mdecPacket = mdecPacket; + _vlc = vlc; + _frameNumber = frameNumber; + _presentationSector = presentationSector; + } + + + public int getWidth() { + return _mdecPacket.getWidth(); + } + + public int getHeight() { + return _mdecPacket.getHeight(); + } + + public @Nonnull MdecInputStream getCustomFrameMdecStream() { + return _vlc.makeFrameBitStreamUncompressor(_mdecPacket); + } + + public @Nonnull FrameNumber getFrame() { + return _frameNumber; + } + + public int getStartSector() { + return _sectors.iStartSector; + } + + public int getEndSector() { + return _sectors.iEndSector; + } + + public @Nonnull Fraction getPresentationSector() { + return _presentationSector; + } + + public int getDemuxSize() { + return _mdecPacket.getPayload().length; + } + + /** Not really useful, but why not. */ + public @Nonnull byte[] copyDemuxData() { + return Arrays.copyOfRange(_mdecPacket.getPayload(), 0, getDemuxSize()); + } + + public void printSectors(PrintStream ps) { + // TODO? + } + + public void writeToSectors(byte[] abNewDemux, int iNewUsedSize, int iNewMdecCodeCount, CdFileSectorReader cd, ILocalizedLogger log) throws LoggedFailure { + throw new UnsupportedOperationException("No support for replacing Road Rash frames"); + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/DiscIndexerRoadRash.java b/jpsxdec/src/jpsxdec/modules/roadrash/DiscIndexerRoadRash.java new file mode 100644 index 0000000..1cf43ec --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/DiscIndexerRoadRash.java @@ -0,0 +1,195 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.discitems.DiscItem; +import jpsxdec.discitems.SerializedDiscItem; +import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.indexing.DiscIndex; +import jpsxdec.indexing.DiscIndexer; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.video.Dimensions; +import jpsxdec.modules.video.framenumber.HeaderFrameNumber; +import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; +import jpsxdec.util.BinaryDataNotRecognized; + +public class DiscIndexerRoadRash extends DiscIndexer implements SectorClaimToRoadRash.Listener { + + private static final Logger LOG = Logger.getLogger(DiscIndexerRoadRash.class.getName()); + + @Override + public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { + SectorClaimToRoadRash ui = scs.getClaimer(SectorClaimToRoadRash.class); + ui.setListener(this); + } + + @Override + public @CheckForNull DiscItem deserializeLineRead(@Nonnull SerializedDiscItem fields) throws LocalizedDeserializationFail { + if (DiscItemRoadRash.TYPE_ID.equals(fields.getType())) + return new DiscItemRoadRash(getCd(), fields); + return null; + } + + @Override + public void listPostProcessing(@Nonnull Collection allItems) { + } + + @Override + public void indexGenerated(@Nonnull DiscIndex index) { + } + + private static class VidBuilder { + @Nonnull + private final Dimensions _dims; + @Nonnull + private final IndexSectorFrameNumber.Format.Builder _indexSectorFrameNumberBuilder; + @Nonnull + private final HeaderFrameNumber.Format.Builder _headerFrameNumberBuilder; + + public VidBuilder(@Nonnull RoadRashPacket.MDEC firstMdec, int iStartSector) { + _dims = new Dimensions(firstMdec.getWidth(), firstMdec.getHeight()); + _indexSectorFrameNumberBuilder = new IndexSectorFrameNumber.Format.Builder(iStartSector); + _headerFrameNumberBuilder = new HeaderFrameNumber.Format.Builder(firstMdec.getFrameNumber()); + } + + public void addFrame(@Nonnull RoadRashPacket.MDEC mdec, int iStartSector) throws BinaryDataNotRecognized { + if (!_dims.equals(mdec.getWidth(), mdec.getHeight()) || + mdec.getFrameNumber() < _headerFrameNumberBuilder.getLastFrameNumber()) + throw new BinaryDataNotRecognized("Road Rash data corruption"); + + _headerFrameNumberBuilder.addHeaderFrameNumber(mdec.getFrameNumber()); + _indexSectorFrameNumberBuilder.addFrameStartSector(iStartSector); + } + + public HeaderFrameNumber.Format getFrameFormat() { + // some movies have the same frame number for all the frames + // if that is the case, then treat it like it has no header frame number + if (_headerFrameNumberBuilder.getStartFrameNumber() != _headerFrameNumberBuilder.getLastFrameNumber()) + return _headerFrameNumberBuilder.makeFormat(); + else + return null; + + } + } + + private static class MovieBuilder { + @CheckForNull + private VidBuilder _vidBuilder; + @Nonnull + private final RoadRashPacket.VLC0 _vlcPacket; + private int _iSpuSoundUnitPairCount = 0; + private final int _iStartSector; + private int _iEndSector; + + public MovieBuilder(RoadRashPacket.VLC0 vlcPacket, int iStartSector) { + this._vlcPacket = vlcPacket; + _iStartSector = iStartSector; + _iEndSector = iStartSector; + } + + public void addPacket(@Nonnull RoadRashPacketSectors packet, int iStartSector) throws BinaryDataNotRecognized { + _iEndSector = packet.iEndSector; + if (packet.packet instanceof RoadRashPacket.AU) { + RoadRashPacket.AU au = (RoadRashPacket.AU)packet.packet; + _iSpuSoundUnitPairCount += au.getSpuSoundUnitPairCount(); + } else if (packet.packet instanceof RoadRashPacket.MDEC) { + if (_vidBuilder == null) { + _vidBuilder = new VidBuilder((RoadRashPacket.MDEC) packet.packet, packet.iStartSector); + } else { + _vidBuilder.addFrame((RoadRashPacket.MDEC) packet.packet, packet.iStartSector); + } + } else if (packet.packet instanceof RoadRashPacket.VLC0) { + throw new BinaryDataNotRecognized(); + } else { + throw new RuntimeException("??"); + } + } + + public @Nonnull DiscItemRoadRash makeItem(@Nonnull CdFileSectorReader cd) throws BinaryDataNotRecognized { + if (_vidBuilder == null) + throw new BinaryDataNotRecognized(); + + DiscItemRoadRash item = new DiscItemRoadRash(cd, _iStartSector, _iEndSector, + _vidBuilder._dims, + _vidBuilder._indexSectorFrameNumberBuilder.makeFormat(), + _vidBuilder.getFrameFormat(), + _iSpuSoundUnitPairCount); + return item; + } + } + + @CheckForNull + private MovieBuilder _movieBuilder; + + public void feedPacket(@Nonnull RoadRashPacketSectors packet, @Nonnull ILocalizedLogger log) { + + try { + if (_movieBuilder == null) { + if (!(packet.packet instanceof RoadRashPacket.VLC0)) { + throw new BinaryDataNotRecognized(); + } + _movieBuilder = new MovieBuilder((RoadRashPacket.VLC0) packet.packet, packet.iStartSector); + } else { + _movieBuilder.addPacket(packet, 0); + } + } catch (BinaryDataNotRecognized ex) { + LOG.log(Level.SEVERE, "Road Rash data corruption", ex); + endVideo(log); + } + } + + public void endVideo(@Nonnull ILocalizedLogger log) { + if (_movieBuilder != null) { + try { + DiscItemRoadRash item = _movieBuilder.makeItem(getCd()); + addDiscItem(item); + } catch (BinaryDataNotRecognized ex) { + LOG.log(Level.SEVERE, "Road Rash data corruption", ex); + } + _movieBuilder = null; + } + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/DiscItemRoadRash.java b/jpsxdec/src/jpsxdec/modules/roadrash/DiscItemRoadRash.java new file mode 100644 index 0000000..b657b70 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/DiscItemRoadRash.java @@ -0,0 +1,276 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.util.Arrays; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.sound.sampled.AudioFormat; +import jpsxdec.adpcm.SpuAdpcmDecoder; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.discitems.SerializedDiscItem; +import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.sharedaudio.DecodedAudioPacket; +import jpsxdec.modules.video.Dimensions; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.modules.video.framenumber.HeaderFrameNumber; +import jpsxdec.modules.video.framenumber.IFrameNumberFormatter; +import jpsxdec.modules.video.framenumber.IFrameNumberFormatterWithHeader; +import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; +import jpsxdec.modules.video.packetbased.DiscItemPacketBasedVideoStream; +import jpsxdec.modules.video.packetbased.SectorClaimToAudioAndFrame; +import jpsxdec.util.Fraction; + +/** A Road Rash audio/video stream. */ +public class DiscItemRoadRash extends DiscItemPacketBasedVideoStream { + + public static final String TYPE_ID = "RoadRash"; + private static final int SECTORS_PER_FRAME = 10; + private static final int FPS = 15; + + /** Will be null if all the header frames were the same number. */ + @CheckForNull + private final HeaderFrameNumber.Format _headerFrameNumberFormat; + + public DiscItemRoadRash(@Nonnull CdFileSectorReader cd, + int iStartSector, int iEndSector, + @Nonnull Dimensions dim, + @Nonnull IndexSectorFrameNumber.Format sectorIndexFrameNumberFormat, + @CheckForNull HeaderFrameNumber.Format headerFrameNumberFormat, + int iSoundUnitCount) + { + super(cd, iStartSector, iEndSector, dim, sectorIndexFrameNumberFormat, iSoundUnitCount); + _headerFrameNumberFormat = headerFrameNumberFormat; + } + + public DiscItemRoadRash(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscItem fields) + throws LocalizedDeserializationFail + { + super(cd, fields); + if (fields.hasField(HeaderFrameNumber.Format.HEADER_FORMAT_KEY)) + _headerFrameNumberFormat = new HeaderFrameNumber.Format(fields); + else + _headerFrameNumberFormat = null; + } + + @Override + public @Nonnull SerializedDiscItem serialize() { + SerializedDiscItem serial = super.serialize(); + if (_headerFrameNumberFormat != null) + _headerFrameNumberFormat.serialize(serial); + return serial; + } + + @Override + public @Nonnull String getSerializationTypeId() { + return TYPE_ID; + } + + @Override + public boolean hasIndependentBitstream() { + return false; + } + + @Override + public @Nonnull FrameNumber getStartFrame() { + if (_headerFrameNumberFormat == null) + return _indexSectorFrameNumberFormat.getStartFrame(); + else + return _headerFrameNumberFormat.getStartFrame(_indexSectorFrameNumberFormat); + } + + @Override + public @Nonnull FrameNumber getEndFrame() { + if (_headerFrameNumberFormat == null) + return _indexSectorFrameNumberFormat.getEndFrame(); + else + return _headerFrameNumberFormat.getEndFrame(_indexSectorFrameNumberFormat); + } + + @Override + public @Nonnull List getFrameNumberTypes() { + if (_headerFrameNumberFormat == null) + return Arrays.asList(FrameNumber.Type.Index, FrameNumber.Type.Sector); + else + return Arrays.asList(FrameNumber.Type.Index, FrameNumber.Type.Header, FrameNumber.Type.Sector); + } + + @Override + protected double getPacketBasedFpsInterestingDescription() { + return FPS; + } + + @Override + public @Nonnull Fraction getSectorsPerFrame() { + return new Fraction(SECTORS_PER_FRAME); + } + + @Override + public double getApproxDuration() { + return getFrameCount() / (double)FPS; + } + + @Override + public int getAudioSampleFramesPerSecond() { + if (hasAudio()) + return RoadRashPacket.SAMPLE_FRAMES_PER_SECOND; + else + return -1; + } + + @Override + public @Nonnull SectorClaimToAudioAndFrame makeAudioVideoDemuxer(double dblVolume) { + if (_headerFrameNumberFormat != null) + return new Demuxer(dblVolume, _headerFrameNumberFormat.makeFormatter(_indexSectorFrameNumberFormat), null); + else + return new Demuxer(dblVolume, null, _indexSectorFrameNumberFormat.makeFormatter()); + } + + public class Demuxer extends SectorClaimToAudioAndFrame + implements SectorClaimToRoadRash.Listener + { + // only one of these two will not be null + @CheckForNull + private final IFrameNumberFormatterWithHeader _fnfwh; + @CheckForNull + private final IFrameNumberFormatter _fnf; + @Nonnull + private final SpuAdpcmDecoder.Stereo _audioDecoder; + + @CheckForNull + private IDemuxedFrame.Listener _frameListener; + @CheckForNull + private DecodedAudioPacket.Listener _audioListener; + + @CheckForNull + private RoadRashPacket.VLC0 _vlcPacket; + + public Demuxer(double dblVolume, + @CheckForNull IFrameNumberFormatterWithHeader fnfwh, + @CheckForNull IFrameNumberFormatter fnf) + { + _audioDecoder = new SpuAdpcmDecoder.Stereo(dblVolume); + _fnfwh = fnfwh; + _fnf = fnf; + } + + public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { + SectorClaimToRoadRash s2cs = scs.getClaimer(SectorClaimToRoadRash.class); + s2cs.setListener(this); + s2cs.setRangeLimit(getStartSector(), getEndSector()); + } + + public void feedPacket(@Nonnull RoadRashPacketSectors packet, @Nonnull ILocalizedLogger log) throws LoggedFailure { + if (packet.packet instanceof RoadRashPacket.AU) { + RoadRashPacket.AU au = (RoadRashPacket.AU)packet.packet; + DecodedAudioPacket aup = au.decode(_audioDecoder); + if (_audioListener != null) + _audioListener.audioPacketComplete(aup, log); + } else if (packet.packet instanceof RoadRashPacket.MDEC) { + RoadRashPacket.MDEC mdecPacket = (RoadRashPacket.MDEC) packet.packet; + + int iFrameNumber = mdecPacket.getFrameNumber(); + FrameNumber fn; + if (_fnf != null) { + fn = _fnf.next(packet.iStartSector, log); + iFrameNumber = fn.getIndexNumber().getFrameValue(); + } else { + fn = _fnfwh.next(packet.iStartSector, mdecPacket.getFrameNumber(), log); + } + + if (_vlcPacket != null && _frameListener != null) { + // problem!!!: we don't have a frame number in some cases + // But is there anything we can really do about it? + // The location of the packet can't really tell us anything + // So I guess we're stuck just using the frame index :P + int iPresentationSector = iFrameNumber * SECTORS_PER_FRAME + DiscItemRoadRash.this.getAbsolutePresentationStartSector(); + _frameListener.frameComplete(new DemuxedRoadRashFrame(packet, mdecPacket, _vlcPacket, fn, new Fraction(iPresentationSector))); + } + } else if (packet.packet instanceof RoadRashPacket.VLC0) { + _vlcPacket = (RoadRashPacket.VLC0)packet.packet; + } + + } + + public void endVideo(@Nonnull ILocalizedLogger log) { + // probably doesn't matter + } + + public void setFrameListener(@CheckForNull IDemuxedFrame.Listener listener) { + _frameListener = listener; + } + + public void setAudioListener(@CheckForNull DecodedAudioPacket.Listener listener) { + _audioListener = listener; + } + + public @Nonnull AudioFormat getOutputFormat() { + return RoadRashPacket.ROAD_RASH_AUDIO_FORMAT; + } + + public double getVolume() { + return _audioDecoder.getVolume(); + } + + public int getAbsolutePresentationStartSector() { + return DiscItemRoadRash.this.getStartSector(); + } + + public int getStartSector() { + return DiscItemRoadRash.this.getStartSector(); + } + + public int getEndSector() { + return DiscItemRoadRash.this.getEndSector(); + } + + public int getSampleFramesPerSecond() { + return RoadRashPacket.SAMPLE_FRAMES_PER_SECOND; + } + + public int getDiscSpeed() { + return 2; + } + + } +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacket.java b/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacket.java new file mode 100644 index 0000000..c6f7dd5 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacket.java @@ -0,0 +1,413 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.sound.sampled.AudioFormat; +import jpsxdec.adpcm.SoundUnitDecoder; +import jpsxdec.adpcm.SpuAdpcmDecoder; +import jpsxdec.adpcm.SpuAdpcmSoundUnit; +import static jpsxdec.modules.roadrash.BitStreamUncompressorRoadRash.ROAD_RASH_BIT_CODE_ORDER; +import jpsxdec.modules.sharedaudio.DecodedAudioPacket; +import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; +import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; +import jpsxdec.psxvideo.bitstreams.ZeroRunLengthAc; +import jpsxdec.psxvideo.bitstreams.ZeroRunLengthAcLookup; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.util.Fraction; +import jpsxdec.util.IO; + +/** + * Support for "Road Rash 3D" videos. + * Road Rash takes a unique approach to bitstreams, every movie has its own unique VLC table. + * There's a lot of big-endian data in here. + */ +public abstract class RoadRashPacket { + + public static final int MIN_PACKET_SIZE = 456; + public static final int MAX_PACKET_SIZE = 14200; + + public static final long MAGIC_VLC0 = 0x564c4330; + public static final long MAGIC_MDEC = 0x4d444543; + public static final long MAGIC_au00 = 0x61753030; + public static final long MAGIC_au01 = 0x61753031; + + public static final int SAMPLE_FRAMES_PER_SECOND = 22050; + public static final int SAMPLE_FRAMES_PER_SECTOR = SAMPLE_FRAMES_PER_SECOND / 150; + static { + if (SAMPLE_FRAMES_PER_SECOND % 150 != 0) + throw new RuntimeException("Road Rash sample rate doesn't cleanly divide by sector rate"); + } + + public static final AudioFormat ROAD_RASH_AUDIO_FORMAT = new AudioFormat(SAMPLE_FRAMES_PER_SECOND, 16, 2, true, false); + + // ========================================================================= + + public static @CheckForNull RoadRashPacket readPacket(@Nonnull InputStream is) + throws EOFException, IOException + { + + long lngMagic = IO.readUInt32BE(is); + if (lngMagic != MAGIC_VLC0 && + lngMagic != MAGIC_au00 && + lngMagic != MAGIC_au01 && + lngMagic != MAGIC_MDEC) + { + return null; + } + + int iPacketSize = IO.readSInt32BE(is); + if (iPacketSize % 4 != 0) + throw new RuntimeException(); + if (iPacketSize < MIN_PACKET_SIZE || iPacketSize > MAX_PACKET_SIZE) + throw new RuntimeException(); + if (lngMagic == MAGIC_au00 || lngMagic == MAGIC_au01) { + return AU.readPacket(lngMagic, iPacketSize, is); + } + if (lngMagic == MAGIC_MDEC) { + return MDEC.readPacket(lngMagic, iPacketSize, is); + } + if (lngMagic == MAGIC_VLC0) { + return VLC0.readPacket(lngMagic, iPacketSize, is); + } + + throw new RuntimeException("Should have been handled before this"); + } + + // ========================================================================= + + public static final int HEADER_SIZEOF = 8; + + private final long _lngPacketType; // @0 4 bytes (BE) + private final int _iHeaderPacketSize; // @4 4 bytes BE + + /** Only payload data that is actual payload, + * not including any additional payload sub-headers */ + @Nonnull + private final byte[] _abPayloadAfterHeaders; + + public RoadRashPacket(long lngPacketType, int iHeaderPacketSize, + @Nonnull byte[] abPayloadAfterHeaders) + { + _lngPacketType = lngPacketType; + _iHeaderPacketSize = iHeaderPacketSize; + _abPayloadAfterHeaders = abPayloadAfterHeaders; + } + + public long getPacketType() { + return _lngPacketType; + } + + public int getHeaderPacketSize() { + return _iHeaderPacketSize; + } + + protected @Nonnull byte[] getPayload() { + return _abPayloadAfterHeaders; + } + + // ========================================================================= + + /** + * "VLC" is known to mean "variable-length code/codes/coding". + * Huffman coding is the kind of VLC logic used in PlayStation games. + */ + public static class VLC0 extends RoadRashPacket { + + public static @CheckForNull VLC0 readPacket(long lngMagic, int iPacketSize, @Nonnull InputStream is) + throws EOFException, IOException + { + if (iPacketSize != (ROAD_RASH_BIT_CODE_ORDER.length * 2) + 8) + throw new RuntimeException(); + byte[] abPacketPayload = IO.readByteArray(is, iPacketSize - 8); + + ZeroRunLengthAcLookup.Builder bldr = new ZeroRunLengthAcLookup.Builder(); + MdecCode[] aoVlcHeaderMdecCodesForReference = new MdecCode[ROAD_RASH_BIT_CODE_ORDER.length]; + + for (int i = 0; i < ROAD_RASH_BIT_CODE_ORDER.length; i++) { + int iMdec = IO.readUInt16LE(abPacketPayload, i * 2); + MdecCode mdecCode = new MdecCode(iMdec); + if (!mdecCode.isValid()) + throw new RuntimeException(); + aoVlcHeaderMdecCodesForReference[i] = mdecCode; + + ZeroRunLengthAc bitCode = new ZeroRunLengthAc(ROAD_RASH_BIT_CODE_ORDER[i], + mdecCode.getTop6Bits(), mdecCode.getBottom10Bits(), + mdecCode.toMdecWord() == BitStreamUncompressorRoadRash.BITSTREAM_ESCAPE_CODE, + mdecCode.isEOD()); + + bldr.add(bitCode); + } + + return new VLC0(lngMagic, iPacketSize, abPacketPayload, bldr.build(), aoVlcHeaderMdecCodesForReference); + } + + @Nonnull + private final ZeroRunLengthAcLookup _vlcLookup; + @Nonnull + private final MdecCode[] _aoVlcHeaderMdecCodesForReference; + + public VLC0(long lngPacketType, int iHeaderPacketSize, + @Nonnull byte[] abPayloadAfterHeaders, + @Nonnull ZeroRunLengthAcLookup vlcLookup, + @Nonnull MdecCode[] aoVlcHeaderMdecCodesForReference) + { + super(lngPacketType, iHeaderPacketSize, abPayloadAfterHeaders); + _vlcLookup = vlcLookup; + _aoVlcHeaderMdecCodesForReference = aoVlcHeaderMdecCodesForReference; + } + + public @Nonnull BitStreamUncompressor makeFrameBitStreamUncompressor(@Nonnull MDEC mdecPacket) { + return new BitStreamUncompressorRoadRash(mdecPacket.getPayload(), _vlcLookup, mdecPacket.getQuantizationScale()); + } + + /** For debugging. */ + public void printCodes() { + for (int i = 0; i < _aoVlcHeaderMdecCodesForReference.length; i++) { + MdecCode mdec = _aoVlcHeaderMdecCodesForReference[i]; + System.out.format("%04X %-8s %s", mdec.toMdecWord(), mdec, ROAD_RASH_BIT_CODE_ORDER[i]); + if (mdec.toMdecWord() == BitStreamUncompressorRoadRash.BITSTREAM_ESCAPE_CODE) + System.out.println(" (escape code)"); + else + System.out.println(); + } + } + + @Override + public String toString() { + return "VLC0"; + } + } + + + // ========================================================================= + + public static class AU extends RoadRashPacket { + + public static @CheckForNull AU readPacket( + long lngMagic, int iPacketSize, @Nonnull InputStream is) + throws EOFException, IOException + { + if (iPacketSize < 1576 || iPacketSize > 6348) + return null; + + int iPresentationSampleFrame = IO.readSInt32BE(is); + if (iPresentationSampleFrame < 0 || iPresentationSampleFrame > 1312556) + throw new RuntimeException(); + + // I don't know what these values are, but they're always the same + int i2048 = IO.readSInt16BE(is); + if (i2048 != 2048) + throw new RuntimeException(); + int i512 = IO.readSInt16BE(is); + if (i512 != 512) + throw new RuntimeException(); + + byte[] abPacketPayload = IO.readByteArray(is, iPacketSize - 8 - 8); + return new AU(lngMagic, iPacketSize, abPacketPayload, iPresentationSampleFrame); + } + + private final int _iPresentationSampleFrame; // @8 4 bytes BE + // 2048 // @12 2 bytes BE + // 512 // @16 2 bytes BE + + public AU(long lngPacketType, int iHeaderPacketSize, + @Nonnull byte[] abPayloadAfterHeaders, int iPresentationSampleFrame) + { + super(lngPacketType, iHeaderPacketSize, abPayloadAfterHeaders); + _iPresentationSampleFrame = iPresentationSampleFrame; + } + + public boolean isLastAudioPacket() { + return getPacketType() == MAGIC_au01; + } + + public int calcSampleFramesGenerated() { + return getSpuSoundUnitPairCount() * SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; + } + + public int getSpuSoundUnitPairCount() { + int iSoundUnits = getPayload().length / 15; + return iSoundUnits / 2; + } + + public @Nonnull DecodedAudioPacket decode(@Nonnull SpuAdpcmDecoder.Stereo decoder) { + List units = unpackSpu(getPayload()); + + // each audio packet is split in half: half for left channel, half for right + int iHalf = units.size() / 2; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (int iUnit = 0; iUnit < iHalf; iUnit++) { + try { + decoder.decode(units.get(iUnit), units.get(iHalf + iUnit), out); + } catch (IOException ex) { + throw new RuntimeException("Should nothappen", ex); + } + } + + if (out.size() != calcSampleFramesGenerated() * 4) + throw new RuntimeException(); + + Fraction presentationSector = new Fraction(_iPresentationSampleFrame, SAMPLE_FRAMES_PER_SECTOR); + + return new DecodedAudioPacket(0, ROAD_RASH_AUDIO_FORMAT, presentationSector, out.toByteArray()); + } + + @Override + public String toString() { + return String.format("au0%c start sample frame:%d sample frames:%d", + isLastAudioPacket() ? '1' : '0', + _iPresentationSampleFrame, calcSampleFramesGenerated()); + } + + } + + /** I guess to make the audio 1/16 smaller they chose to remove the + * 2nd byte in each SPU sound unit, which is usually 0, but I found + * it can also be filled with other values, but haven't found where + * that logic lies. */ + private static @Nonnull List unpackSpu(@Nonnull byte[] abPayload) { + List units = new ArrayList(); + + int iBytesUsed; + for (iBytesUsed = 0; iBytesUsed+14 < abPayload.length; iBytesUsed+=15) { + byte[] abSpuSoundUnit = new byte[16]; + abSpuSoundUnit[0] = abPayload[iBytesUsed]; + // this byte can sometimes be values other than 0 + // I couldn't figure out how the game decides to make + // those changes. Where would the data come from? + // Maybe the leftover 2 bytes in some audio packets? + // Maybe the timestamp? + // More debugging is nedded + abSpuSoundUnit[1] = 0; + System.arraycopy(abPayload, iBytesUsed+1, abSpuSoundUnit, 2, 14); + units.add(new SpuAdpcmSoundUnit(abSpuSoundUnit)); + } + + return units; + } + + // ========================================================================= + + public static class MDEC extends RoadRashPacket { + + public static @CheckForNull MDEC readPacket( + long lngMagic, int iPacketSize, @Nonnull InputStream is) + throws EOFException, IOException + { + if (iPacketSize < 688 || iPacketSize > 14200) + throw new RuntimeException(); + + int iWidth = IO.readSInt16BE(is); + int iHeight = IO.readSInt16BE(is); + + if ((iWidth != 144 || iHeight != 112) && + (iWidth != 208 || iHeight != 144) && + (iWidth != 320 || iHeight != 144) && + (iWidth != 320 || iHeight != 224)) + { + throw new RuntimeException(); + } + + int iFrameNumber = IO.readSInt32BE(is); + if ((iFrameNumber < 0 || iFrameNumber > 893) && iFrameNumber != 0x7fffad1c) + throw new RuntimeException(); + + byte[] abStrHeader = IO.readByteArray(is, BitStreamUncompressor_STRv2.StrV2Header.SIZEOF); + + BitStreamUncompressor_STRv2.StrV2Header strHeader = + new BitStreamUncompressor_STRv2.StrV2Header(abStrHeader, abStrHeader.length); + + if (!strHeader.isValid()) + throw new RuntimeException(); + if (strHeader.getQuantizationScale() < 1 || strHeader.getQuantizationScale() > 17) + throw new RuntimeException(); + + byte[] abPacketPayload = IO.readByteArray(is, iPacketSize - 8 - 8 - abStrHeader.length); + return new MDEC(lngMagic, iPacketSize, abPacketPayload, iWidth, iHeight, iFrameNumber, strHeader); + } + + private final int _iWidth; // @8 2 bytes BE + private final int _iHeight; // @10 2 bytes BE + private final int _iFrameNumber; // @12 4 bytes + @Nonnull + private final BitStreamUncompressor_STRv2.StrV2Header _strHeader; // @16 8 bytes + + public MDEC(long lngPacketType, int iHeaderPacketSize, + @Nonnull byte[] abPayloadAfterHeaders, + int iWidth, int iHeight, int iFrameNumber, + @Nonnull BitStreamUncompressor_STRv2.StrV2Header strHeader) + { + super(lngPacketType, iHeaderPacketSize, abPayloadAfterHeaders); + _iWidth = iWidth; + _iHeight = iHeight; + _iFrameNumber = iFrameNumber; + _strHeader = strHeader; + } + + public int getWidth() { + return _iWidth; + } + + public int getHeight() { + return _iHeight; + } + + public int getFrameNumber() { + return _iFrameNumber; + } + + public int getQuantizationScale() { + return _strHeader.getQuantizationScale(); + } + + @Override + public String toString() { + return String.format("MDEC %dx%d frame:%d qscale:%d", + _iWidth, _iHeight, _iFrameNumber, _strHeader.getQuantizationScale()); + } + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacketSectors.java b/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacketSectors.java new file mode 100644 index 0000000..c486aab --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/RoadRashPacketSectors.java @@ -0,0 +1,53 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import javax.annotation.Nonnull; + +/** Wraps a standard {@link RoadRashPacket} with the sectors it was found in. */ +public class RoadRashPacketSectors { + @Nonnull + public final RoadRashPacket packet; + public final int iStartSector, iEndSector; + + public RoadRashPacketSectors(@Nonnull RoadRashPacket packet, int iStartSector, int iEndSector) { + this.packet = packet; + this.iStartSector = iStartSector; + this.iEndSector = iEndSector; + } +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/SectorClaimToRoadRash.java b/jpsxdec/src/jpsxdec/modules/roadrash/SectorClaimToRoadRash.java new file mode 100644 index 0000000..bbec29a --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/SectorClaimToRoadRash.java @@ -0,0 +1,234 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.io.EOFException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; +import jpsxdec.modules.CdSectorDemuxPiece; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.util.DemuxPushInputStream; +import jpsxdec.util.DemuxPushInputStream.NeedsMoreData; +import jpsxdec.util.IO; +import jpsxdec.util.IOIterator; + +public class SectorClaimToRoadRash extends SectorClaimSystem.SectorClaimer { + + private static final Logger LOG = Logger.getLogger(SectorClaimToRoadRash.class.getName()); + + public interface Listener { + void feedPacket(@Nonnull RoadRashPacketSectors packet, @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void endVideo(@Nonnull ILocalizedLogger log); + } + + @CheckForNull + private Listener _listener; + + public SectorClaimToRoadRash() { + } + public SectorClaimToRoadRash(@Nonnull Listener listener) { + _listener = listener; + } + public void setListener(@CheckForNull Listener listener) { + _listener = listener; + } + + @CheckForNull + private DemuxPushInputStream _sectorStream; + private boolean _blnVidEnd = false; + + private @Nonnull List readAsManyPacketsAsPossible() { + List finishedPackets = new ArrayList(); + if (_sectorStream == null) throw new IllegalStateException(); + + if (_sectorStream.available() < RoadRashPacket.MIN_PACKET_SIZE) + return finishedPackets; + + while (true) { + _sectorStream.mark(RoadRashPacket.MAX_PACKET_SIZE); + int iStartSector = _sectorStream.getCurrentPiece().getSectorNumber(); + + try { + RoadRashPacket packet = RoadRashPacket.readPacket(_sectorStream); + + if (packet != null) { + int iEndSector = _sectorStream.getCurrentPiece().getSectorNumber(); + finishedPackets.add(new RoadRashPacketSectors(packet, iStartSector, iEndSector)); + } else { + + // possibly end of stream + _sectorStream.reset(); + + // check for 8 zeroes + int iIsZero = IO.readSInt32BE(_sectorStream); + if (iIsZero == 0) { + _sectorStream.mark(RoadRashPacket.MAX_PACKET_SIZE); + iIsZero = IO.readSInt32BE(_sectorStream); + if (iIsZero == 0) { + // 8 zeroes, definitely end of stream + _blnVidEnd = true; + return finishedPackets; + } + + // so there were 4 zeroes, but now there is some non-zero data + // don't end the stream, but this is definitely data corruption + LOG.warning("Corrupted Road Rash stream"); + _sectorStream.reset(); // do another loop + } + } + + } catch (NeedsMoreData ex) { + // backup and try again when there's more data + _sectorStream.reset(); + return finishedPackets; + } catch (EOFException ex) { + // stream was closed and we hit the end + return finishedPackets; + } catch (IOException ex) { + throw new RuntimeException("Should not happen"); + } + + } + } + + public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, + @Nonnull IOIterator peekIt, + @Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + CdSector cdSector = cs.getSector(); + if (cs.getClaimer() != null || cdSector.isCdAudioSector()) { + // close any existing stream + endVideo(log); + return; + } + + int iStartEnd = 0; + + if (_sectorStream == null) { + // No current movie + + long lngMagic = cdSector.readUInt32BE(0); + if (lngMagic == RoadRashPacket.MAGIC_VLC0) { + // Probably a movie + + DemuxPushInputStream stream = new DemuxPushInputStream(new CdSectorDemuxPiece(cdSector)); + RoadRashPacket firstPacket; + try { + firstPacket = RoadRashPacket.readPacket(stream); + } catch (IOException ex) { + throw new RuntimeException("Should not happen"); + } + + if (firstPacket instanceof RoadRashPacket.VLC0) { + // Definitely a movie + // send first packet to listener + _sectorStream = stream; + iStartEnd = SectorRoadRash.START; + if (_listener != null) { + try { + _listener.feedPacket(new RoadRashPacketSectors(firstPacket, + cdSector.getSectorIndexFromStart(), cdSector.getSectorIndexFromStart()), log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } + } + } + + } else { + // add to existing stream + _sectorStream.addPiece(new CdSectorDemuxPiece(cdSector)); + } + + if (_sectorStream != null) { + + List finishedPackets = readAsManyPacketsAsPossible(); + + List rrps = new ArrayList(finishedPackets.size()); + for (RoadRashPacketSectors finishedPacket : finishedPackets) { + if (finishedPacket.packet instanceof RoadRashPacket.VLC0) { + throw new RuntimeException("Should not happen unless data is corrupted"); + } + + if (_listener != null) { + // Only send packets that are fully in the active sector range + // (in practice there should never be a packet crossing the border) + if (sectorIsInRange(finishedPacket.iStartSector) && + sectorIsInRange(finishedPacket.iEndSector)) + { + try { + _listener.feedPacket(finishedPacket, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } + } + + rrps.add(finishedPacket.packet); + } + + if (_blnVidEnd) + iStartEnd |= SectorRoadRash.END; + cs.claim(new SectorRoadRash(cdSector, rrps, iStartEnd)); + if (_blnVidEnd) + endVideo(log); + } + } + + private void endVideo(@Nonnull ILocalizedLogger log) { + if (_sectorStream != null) { + _blnVidEnd = false; + _sectorStream = null; + if (_listener != null) + _listener.endVideo(log); + } + } + + public void endOfSectors(@Nonnull ILocalizedLogger log) { + endVideo(log); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/roadrash/SectorRoadRash.java b/jpsxdec/src/jpsxdec/modules/roadrash/SectorRoadRash.java new file mode 100644 index 0000000..01b1460 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/roadrash/SectorRoadRash.java @@ -0,0 +1,84 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.roadrash; + +import java.util.List; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.modules.IdentifiedSector; + +/** Just a simple sector wrapper for Road Rash to claim sectors. */ +public class SectorRoadRash extends IdentifiedSector { + + /** If the sector is at the start, end, or middle (0) of the stream (bit flags). */ + public static final int START = 1, END = 2; + + private final int _iStartEnd; + @Nonnull + private final List _packetsEndingInThisSector; + + public SectorRoadRash(@Nonnull CdSector cdSector, @Nonnull List packetsEndingInThisSector, int iStartEnd) { + super(cdSector); + _packetsEndingInThisSector = packetsEndingInThisSector; + _iStartEnd = iStartEnd; + setProbability(100); + } + + public String getTypeName() { + return "RoadRash"; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getTypeName()).append(' ') + .append(super.toString()).append(' ') + .append(_packetsEndingInThisSector.size()).append(" finished packets ["); + for (int i = 0; i < _packetsEndingInThisSector.size(); i++) { + if (i > 0) + sb.append(", "); + sb.append(_packetsEndingInThisSector.get(i)); + } + sb.append(']'); + if ((_iStartEnd & START) != 0) + sb.append(" START"); + if ((_iStartEnd & END) != 0) + sb.append(" END"); + return sb.toString(); + } +} diff --git a/jpsxdec/src/jpsxdec/modules/sharedaudio/AudioSaverBuilder.java b/jpsxdec/src/jpsxdec/modules/sharedaudio/AudioSaverBuilder.java index 28c188b..b609e99 100644 --- a/jpsxdec/src/jpsxdec/modules/sharedaudio/AudioSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/sharedaudio/AudioSaverBuilder.java @@ -176,7 +176,7 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb throw new NumberFormatException(); setVolume(iVol / 100.0); } catch (NumberFormatException ex) { - fbs.printlnWarn(I.CMD_IGNORING_INVALID_VOLUME(vol.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(vol.value, "-vol")); } } @@ -185,14 +185,14 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb if (fmt != null) { setContainerForamt(fmt); } else { - fbs.printlnWarn(I.CMD_IGNORING_INVALID_FORMAT(audfmt.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(audfmt.value, "-af,-audfmt")); } } } - public void printSelectedOptions(@Nonnull FeedbackStream fbs) { - fbs.println(I.CMD_AUDIO_FORMAT(_containerFormat.getCmdId())); - fbs.println(I.CMD_VOLUME_PERCENT(getVolume())); - fbs.println(I.CMD_FILENAME(getFileRelativePath())); + public void printSelectedOptions(@Nonnull ILocalizedLogger log) { + log.log(Level.INFO, I.CMD_AUDIO_FORMAT(_containerFormat.getCmdId())); + log.log(Level.INFO, I.CMD_VOLUME_PERCENT(getVolume())); + log.log(Level.INFO, I.CMD_FILENAME(getFileRelativePath())); } public @Nonnull ILocalizedMessage getOutputSummary() { @@ -208,9 +208,9 @@ public void startSave(final @Nonnull ProgressLogger pl, @CheckForNull File outpu throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); + final File outputFile = new File(outputDir, getFileRelativePath().getPath()); - ISectorAudioDecoder decoder = _audItem.makeDecoder(getVolume()); - AudioFormat audioFmt = decoder.getOutputFormat(); try { IO.makeDirsForFile(outputFile); @@ -218,6 +218,10 @@ public void startSave(final @Nonnull ProgressLogger pl, @CheckForNull File outpu throw new LoggedFailure(pl, Level.SEVERE, ex.getSourceMessage(), ex); } + // SectorClaimSystem -> ISectorAudioDecoder -> DecodedAudioPacket -> AudioOutputFileWriter + + ISectorAudioDecoder decoder = _audItem.makeDecoder(getVolume()); + AudioFormat audioFmt = decoder.getOutputFormat(); final AudioOutputFileWriter audioWriter; try { audioWriter = new AudioOutputFileWriter(outputFile, diff --git a/jpsxdec/src/jpsxdec/modules/sharedaudio/DecodedAudioPacket.java b/jpsxdec/src/jpsxdec/modules/sharedaudio/DecodedAudioPacket.java index 455d1b3..0882122 100644 --- a/jpsxdec/src/jpsxdec/modules/sharedaudio/DecodedAudioPacket.java +++ b/jpsxdec/src/jpsxdec/modules/sharedaudio/DecodedAudioPacket.java @@ -39,6 +39,7 @@ import javax.annotation.Nonnull; import javax.sound.sampled.AudioFormat; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.util.Fraction; @@ -47,7 +48,8 @@ public class DecodedAudioPacket { public interface Listener { void audioPacketComplete(@Nonnull DecodedAudioPacket packet, - @Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; } /** Channel that this audio packet belongs to. diff --git a/jpsxdec/src/jpsxdec/modules/sharedaudio/DiscItemAudioStream.java b/jpsxdec/src/jpsxdec/modules/sharedaudio/DiscItemAudioStream.java index 7db388c..2c8fd59 100644 --- a/jpsxdec/src/jpsxdec/modules/sharedaudio/DiscItemAudioStream.java +++ b/jpsxdec/src/jpsxdec/modules/sharedaudio/DiscItemAudioStream.java @@ -119,7 +119,7 @@ public ILocalizedMessage getDetails() { } public @Nonnull PlayController makePlayController() { - return new PlayController(new MediaPlayer(this)); + return new MediaPlayer(this).getPlayController(); } @Override diff --git a/jpsxdec/src/jpsxdec/modules/sharedaudio/ISectorAudioDecoder.java b/jpsxdec/src/jpsxdec/modules/sharedaudio/ISectorAudioDecoder.java index 2305b08..ed39367 100644 --- a/jpsxdec/src/jpsxdec/modules/sharedaudio/ISectorAudioDecoder.java +++ b/jpsxdec/src/jpsxdec/modules/sharedaudio/ISectorAudioDecoder.java @@ -59,9 +59,9 @@ public interface ISectorAudioDecoder { /** Sector where the audio begins to play. */ int getAbsolutePresentationStartSector(); - @Deprecated + /** Physical start sector, not presentation start sector. */ int getStartSector(); - @Deprecated + /** Physical end sector, not presentation end sector. */ int getEndSector(); int getSampleFramesPerSecond(); diff --git a/jpsxdec/src/jpsxdec/modules/spu/DiscIndexerSpu.java b/jpsxdec/src/jpsxdec/modules/spu/DiscIndexerSpu.java index a97d4cb..ade640c 100644 --- a/jpsxdec/src/jpsxdec/modules/spu/DiscIndexerSpu.java +++ b/jpsxdec/src/jpsxdec/modules/spu/DiscIndexerSpu.java @@ -147,7 +147,7 @@ public void addQuad(int iQuadIndex, int iQuad, int iSector, int iOffset) { _blnOnlyZeroes = (iQuad & 0xffff) == 0; } } else if (_blnInRun) { - _blnOnlyZeroes &= iQuad == 0; + _blnOnlyZeroes = _blnOnlyZeroes && iQuad == 0; if (iQuadIndex == 3) { _iEndSector = iSector; _iEndOffset = iOffset + 3; diff --git a/jpsxdec/src/jpsxdec/modules/spu/DiscItemSpu.java b/jpsxdec/src/jpsxdec/modules/spu/DiscItemSpu.java index bbaf809..bdc3b74 100644 --- a/jpsxdec/src/jpsxdec/modules/spu/DiscItemSpu.java +++ b/jpsxdec/src/jpsxdec/modules/spu/DiscItemSpu.java @@ -54,6 +54,7 @@ import jpsxdec.i18n.UnlocalizedMessage; import jpsxdec.i18n.exception.LocalizedDeserializationFail; import jpsxdec.util.ExposedBAOS; +import jpsxdec.util.Misc; /** Represents a PlayStation Sound Processing Unit (SPU) audio clip. * There's no way to know the sample rate of SPU clips, so the user @@ -147,7 +148,7 @@ public void setSampleRate(int iSampleRate) { public @Nonnull ILocalizedMessage getInterestingDescription() { int iPcmSampleCount = _iSoundUnitCount * SoundUnitDecoder.SAMPLES_PER_SOUND_UNIT; double dblApproxDuration = iPcmSampleCount / (double)_iSampleRate; - Date secs = new Date(0, 0, 0, 0, 0, Math.max((int)dblApproxDuration, 1)); + Date secs = Misc.dateFromSeconds(Math.max((int)dblApproxDuration, 1)); return new UnlocalizedMessage(MessageFormat.format("{0} samples at {1} Hz = {2,time,m:ss}", iPcmSampleCount, _iSampleRate, secs)); // I18N } diff --git a/jpsxdec/src/jpsxdec/modules/spu/SpuSaverBuilder.java b/jpsxdec/src/jpsxdec/modules/spu/SpuSaverBuilder.java index 7072b73..02f97e4 100644 --- a/jpsxdec/src/jpsxdec/modules/spu/SpuSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/spu/SpuSaverBuilder.java @@ -125,6 +125,7 @@ private SpuSaverFormat(@Nonnull JavaAudioFormat jFmt) { return _sExtension; } + @Override public String toString() { if (_jFmt != null) return _jFmt.toString(); @@ -318,14 +319,14 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb // TODO } } - public void printSelectedOptions(@Nonnull FeedbackStream fbs) { + public void printSelectedOptions(@Nonnull ILocalizedLogger log) { SpuSaverFormat fmt = getContainerFormat(); JavaAudioFormat jFmt = fmt.getJavaType(); if (jFmt != null) { - fbs.println(I.CMD_VOLUME_PERCENT(_dblVolume)); - fbs.println(I.CMD_AUDIO_FORMAT(jFmt.getCmdId())); + log.log(Level.INFO, I.CMD_VOLUME_PERCENT(_dblVolume)); + log.log(Level.INFO, I.CMD_AUDIO_FORMAT(jFmt.getCmdId())); } - fbs.println(I.CMD_FILENAME(getFileRelativePath())); + log.log(Level.INFO, I.CMD_FILENAME(getFileRelativePath())); } public @Nonnull ILocalizedMessage getOutputSummary() { @@ -341,6 +342,7 @@ public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File directory) throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); pl.progressStart(1); File outputFile = new File(directory, getFileRelativePath().getPath()); diff --git a/jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquare.java b/jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquareAudio.java similarity index 98% rename from jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquare.java rename to jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquareAudio.java index 41a7626..48e2fef 100644 --- a/jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquare.java +++ b/jpsxdec/src/jpsxdec/modules/square/DiscIndexerSquareAudio.java @@ -52,7 +52,7 @@ /** Watches for Square's unique audio format streams. * All known games that use Square audio run their streaming media at 2x * disc speed. */ -public class DiscIndexerSquare extends DiscIndexer +public class DiscIndexerSquareAudio extends DiscIndexer implements SquareAudioSectorToSquareAudioSectorPair.Listener { @@ -110,7 +110,7 @@ public boolean pairDone(@Nonnull SquareAudioSectorPair pair) { @CheckForNull private AudioBuilder _audBldr; - public DiscIndexerSquare(@Nonnull ILocalizedLogger errLog) { + public DiscIndexerSquareAudio(@Nonnull ILocalizedLogger errLog) { _errLog = errLog; } diff --git a/jpsxdec/src/jpsxdec/modules/square/DiscItemSquareAudioStream.java b/jpsxdec/src/jpsxdec/modules/square/DiscItemSquareAudioStream.java index 00d5873..48dd8df 100644 --- a/jpsxdec/src/jpsxdec/modules/square/DiscItemSquareAudioStream.java +++ b/jpsxdec/src/jpsxdec/modules/square/DiscItemSquareAudioStream.java @@ -67,6 +67,7 @@ import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; import jpsxdec.util.IO; import jpsxdec.util.IncompatibleException; +import jpsxdec.util.Misc; import jpsxdec.util.TaskCanceledException; /** Represents a series of Square SPU ADPCM sectors that combine to make an audio stream. @@ -155,7 +156,7 @@ public int getSampleFramesPerSecond() { @Override public @Nonnull ILocalizedMessage getInterestingDescription() { // unable to find ANY sources of info about how to localize durations - Date secs = new Date(0, 0, 0, 0, 0, (int)Math.max(getSampleFrameCount() / _iSampleFramesPerSecond, 1)); + Date secs = Misc.dateFromSeconds((int)Math.max(getSampleFrameCount() / _iSampleFramesPerSecond, 1)); return I.GUI_AUDIO_DESCRIPTION(secs, _iSampleFramesPerSecond, 2); } diff --git a/jpsxdec/src/jpsxdec/modules/square/SectorChronoXVideo.java b/jpsxdec/src/jpsxdec/modules/square/SectorChronoXVideo.java index 63a3115..3b0e6b0 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SectorChronoXVideo.java +++ b/jpsxdec/src/jpsxdec/modules/square/SectorChronoXVideo.java @@ -82,10 +82,10 @@ public SectorChronoXVideo(@Nonnull CdSector cdSector) { _header.lngMagic != CHRONO_CROSS_VIDEO_CHUNK_MAGIC2) return; - if (!_header.isChunkNumberStandard()) return; - if (!_header.isChunksInFrameStandard()) return; - if (!_header.isFrameNumberStandard()) return; - if (!_header.isUsedDemuxSizeStandard()) return; + if (!_header.hasStandardChunkNumber()) return; + if (!_header.hasStandardChunksInFrame()) return; + if (!_header.hasStandardFrameNumber()) return; + if (!_header.hasStandardUsedDemuxSize()) return; _iWidth = cdSector.readSInt16LE(16); if (_iWidth < 1) return; _iHeight = cdSector.readSInt16LE(18); diff --git a/jpsxdec/src/jpsxdec/modules/square/SectorClaimToSquareAudioSector.java b/jpsxdec/src/jpsxdec/modules/square/SectorClaimToSquareAudioSector.java index c856140..3c2e4b1 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SectorClaimToSquareAudioSector.java +++ b/jpsxdec/src/jpsxdec/modules/square/SectorClaimToSquareAudioSector.java @@ -41,6 +41,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.cdreaders.CdSector; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.IOIterator; @@ -49,8 +50,9 @@ public class SectorClaimToSquareAudioSector extends SectorClaimSystem.SectorClaimer { public interface Listener { - void sectorRead(@Nonnull ISquareAudioSector squareAudioSector, @Nonnull ILocalizedLogger log); - void endOfSectors(@Nonnull ILocalizedLogger log); + void sectorRead(@Nonnull ISquareAudioSector squareAudioSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure; } public static @CheckForNull ISquareAudioSector id(@Nonnull CdSector sector) { @@ -76,7 +78,7 @@ public void setListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { if (cs.isClaimed()) return; @@ -84,13 +86,25 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, if (audSector == null) return; cs.claim(audSector); - if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) - _listener.sectorRead(audSector, log); + if (_listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) { + try { + _listener.sectorRead(audSector, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } - public void endOfSectors(@Nonnull ILocalizedLogger log) { - if (_listener != null) - _listener.endOfSectors(log); + public void endOfSectors(@Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + if (_listener != null) { + try { + _listener.endOfSectors(log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } } diff --git a/jpsxdec/src/jpsxdec/modules/square/SectorFF9.java b/jpsxdec/src/jpsxdec/modules/square/SectorFF9.java index 9cabc5d..1cfc776 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SectorFF9.java +++ b/jpsxdec/src/jpsxdec/modules/square/SectorFF9.java @@ -51,6 +51,7 @@ import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; import jpsxdec.modules.video.sectorbased.SectorBasedFrameReplace; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; +import jpsxdec.psxvideo.mdec.Calc; import jpsxdec.util.ByteArrayFPIS; import jpsxdec.util.IO; @@ -176,6 +177,7 @@ public SectorFF9Video(@Nonnull CdSector cdSector) { // .. Public functions ................................................. + @Override public String toString() { return String.format( "%s %s frame:%d chunk:%d/%d %dx%d ver:%d " + @@ -245,8 +247,8 @@ public void replaceVideoSectorHeader(@Nonnull byte[] abNewDemuxData, int iNewUse IO.writeInt32LE(abCurrentVidSectorHeader, 12, iDemuxSizeForHeader / 4); IO.writeInt16LE(abCurrentVidSectorHeader, 20, - BitStreamUncompressor_STRv2.calculateHalfCeiling32(iNewMdecCodeCount)); - IO.writeInt16LE(abCurrentVidSectorHeader, 24, (short)(header.getQscale())); + Calc.calculateHalfCeiling32(iNewMdecCodeCount)); + IO.writeInt16LE(abCurrentVidSectorHeader, 24, (short)(header.getQuantizationScale())); } } diff --git a/jpsxdec/src/jpsxdec/modules/square/SquareAKAOstruct.java b/jpsxdec/src/jpsxdec/modules/square/SquareAKAOstruct.java index e52c396..5a80781 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SquareAKAOstruct.java +++ b/jpsxdec/src/jpsxdec/modules/square/SquareAKAOstruct.java @@ -64,6 +64,7 @@ public SquareAKAOstruct(@Nonnull CdSector cdSector, int iReadPos) { BytesOfData = cdSector.readUInt32LE(iReadPos+32); } + @Override public String toString() { return String.format( "AKAO:%s frame-1:%d ?:%04x Size:%d", diff --git a/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorPairToAudioPacket.java b/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorPairToAudioPacket.java index 37f802d..7a78c60 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorPairToAudioPacket.java +++ b/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorPairToAudioPacket.java @@ -45,6 +45,7 @@ import javax.sound.sampled.AudioFormat; import jpsxdec.adpcm.SpuAdpcmDecoder; import jpsxdec.i18n.I; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.sharedaudio.DecodedAudioPacket; import jpsxdec.util.Fraction; @@ -65,7 +66,9 @@ public void setListener(@CheckForNull DecodedAudioPacket.Listener listener) { _listener = listener; } - public void pairDone(@Nonnull SquareAudioSectorPair pair, @Nonnull ILocalizedLogger log) { + public void pairDone(@Nonnull SquareAudioSectorPair pair, @Nonnull ILocalizedLogger log) + throws LoggedFailure + { _buffer.reset(); try { long lngSamplesWritten = _decoder.getSampleFramesWritten(); diff --git a/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorToSquareAudioSectorPair.java b/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorToSquareAudioSectorPair.java index 0dd490f..c47abfa 100644 --- a/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorToSquareAudioSectorPair.java +++ b/jpsxdec/src/jpsxdec/modules/square/SquareAudioSectorToSquareAudioSectorPair.java @@ -39,12 +39,14 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; public class SquareAudioSectorToSquareAudioSectorPair implements SectorClaimToSquareAudioSector.Listener { public interface Listener { - void pairDone(@Nonnull SquareAudioSectorPair pair, @Nonnull ILocalizedLogger log); + void pairDone(@Nonnull SquareAudioSectorPair pair, @Nonnull ILocalizedLogger log) + throws LoggedFailure; void endOfSectors(@Nonnull ILocalizedLogger log); } @@ -62,7 +64,9 @@ public void setListener(@CheckForNull Listener listener) { _listener = listener; } - public void sectorRead(@Nonnull ISquareAudioSector audSector, @Nonnull ILocalizedLogger log) { + public void sectorRead(@Nonnull ISquareAudioSector audSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure + { if (_leftAudioSector != null) { if (isPair(_leftAudioSector, audSector)) { @@ -98,7 +102,7 @@ public void sectorRead(@Nonnull ISquareAudioSector audSector, @Nonnull ILocalize } } - private void leftOnlyDone(@Nonnull ILocalizedLogger log) { + private void leftOnlyDone(@Nonnull ILocalizedLogger log) throws LoggedFailure { _listener.pairDone(new SquareAudioSectorPair( _leftAudioSector, null, _leftAudioSector.getHeaderFrameNumber(), @@ -119,7 +123,7 @@ private static boolean isPair(@Nonnull ISquareAudioSector left, @Nonnull ISquare left.getSectorNumber() + 1 == right.getSectorNumber(); } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + public void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure { if (_listener != null) { if (_leftAudioSector != null) leftOnlyDone(log); diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/DiscIndexerStrVideo.java b/jpsxdec/src/jpsxdec/modules/strvideo/DiscIndexerStrVideo.java index fd5ece9..2c6a2df 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/DiscIndexerStrVideo.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/DiscIndexerStrVideo.java @@ -73,6 +73,8 @@ private static class VidBuilder { @Nonnull private final SectorBasedVideoInfoBuilder _vidInfoBuilder; private int _iLastFrameNumber; + private final boolean _blnHasSpecialBs; + public VidBuilder(@Nonnull DemuxedFrameWithNumberAndDims firstFrame) { _iLastFrameNumber = firstFrame.getHeaderFrameNumber(); @@ -81,6 +83,7 @@ public VidBuilder(@Nonnull DemuxedFrameWithNumberAndDims firstFrame) { firstFrame.getStartSector(), firstFrame.getEndSector()); _indexSectorFrameNumberBuilder = new IndexSectorFrameNumber.Format.Builder(firstFrame.getStartSector()); _headerFrameNumberBuilder = new HeaderFrameNumber.Format.Builder(firstFrame.getHeaderFrameNumber()); + _blnHasSpecialBs = firstFrame.getCustomFrameMdecStream() != null; } /** @return if the frame was accepted as part of this video, otherwise start a new video. */ @@ -96,6 +99,8 @@ public boolean addFrame(@Nonnull DemuxedFrameWithNumberAndDims frame) { // a huge gap in frame numbers return false; } + if (_blnHasSpecialBs && frame.getCustomFrameMdecStream() == null) + return false; _iLastFrameNumber = frame.getHeaderFrameNumber(); _vidInfoBuilder.next(frame.getStartSector(), frame.getEndSector()); _indexSectorFrameNumberBuilder.addFrameStartSector(frame.getStartSector()); @@ -109,7 +114,8 @@ public boolean addFrame(@Nonnull DemuxedFrameWithNumberAndDims frame) { _vidInfoBuilder.makeDims(), _indexSectorFrameNumberBuilder.makeFormat(), _vidInfoBuilder.makeStrVidInfo(), - _headerFrameNumberBuilder.makeFormat()); + _headerFrameNumberBuilder.makeFormat(), + !_blnHasSpecialBs); } } diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/DiscItemStrVideoStream.java b/jpsxdec/src/jpsxdec/modules/strvideo/DiscItemStrVideoStream.java index 9abe1e0..20a347a 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/DiscItemStrVideoStream.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/DiscItemStrVideoStream.java @@ -46,6 +46,7 @@ import jpsxdec.discitems.DiscItem; import jpsxdec.discitems.SerializedDiscItem; import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.DebugLogger; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.IIdentifiedSector; @@ -78,15 +79,19 @@ public class DiscItemStrVideoStream extends DiscItemSectorBasedVideoStream { @Nonnull private final HeaderFrameNumber.Format _headerFrameNumberFormat; + private static final String INDEPENDENT_BITSTREAM = "Independent bitstream"; + private final boolean _blnHasIndependentBitstream; public DiscItemStrVideoStream(@Nonnull CdFileSectorReader cd, int iStartSector, int iEndSector, @Nonnull Dimensions dim, @Nonnull IndexSectorFrameNumber.Format indexSectorFrameNumberFormat, @Nonnull SectorBasedVideoInfo strVidInfo, - @Nonnull HeaderFrameNumber.Format headerFrameNumberFormat) + @Nonnull HeaderFrameNumber.Format headerFrameNumberFormat, + boolean blnHasIndependentBitstream) { super(cd, iStartSector, iEndSector, dim, indexSectorFrameNumberFormat, strVidInfo); _headerFrameNumberFormat = headerFrameNumberFormat; + _blnHasIndependentBitstream = blnHasIndependentBitstream; } public DiscItemStrVideoStream(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscItem fields) @@ -94,12 +99,18 @@ public DiscItemStrVideoStream(@Nonnull CdFileSectorReader cd, @Nonnull Serialize { super(cd, fields); _headerFrameNumberFormat = new HeaderFrameNumber.Format(fields); + if (fields.hasField(INDEPENDENT_BITSTREAM)) + _blnHasIndependentBitstream = fields.getYesNo(INDEPENDENT_BITSTREAM); + else + _blnHasIndependentBitstream = true; } @Override public @Nonnull SerializedDiscItem serialize() { SerializedDiscItem serial = super.serialize(); _headerFrameNumberFormat.serialize(serial); + if (!_blnHasIndependentBitstream) + serial.addYesNo(INDEPENDENT_BITSTREAM, _blnHasIndependentBitstream); return serial; } @@ -108,6 +119,11 @@ public DiscItemStrVideoStream(@Nonnull CdFileSectorReader cd, @Nonnull Serialize return TYPE_ID; } + @Override + public boolean hasIndependentBitstream() { + return _blnHasIndependentBitstream; + } + @Override public int getParentRating(@Nonnull DiscItem child) { if (!(child instanceof DiscItemAudioStream)) @@ -204,7 +220,9 @@ public void attachToSectorClaimer(@Nonnull SectorClaimSystem scs) { s2sv.setListener(new StrVideoSectorToDemuxedStrFrame(this)); } - public void frameComplete(@Nonnull DemuxedFrameWithNumberAndDims frame, @Nonnull ILocalizedLogger log) { + public void frameComplete(@Nonnull DemuxedFrameWithNumberAndDims frame, @Nonnull ILocalizedLogger log) + throws LoggedFailure + { FrameNumber fn = _frameNumberFormatter.next(frame.getStartSector(), frame.getHeaderFrameNumber(), log); frame.setFrame(fn); diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorAliceNullVideo.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorAliceNullVideo.java index 43de65c..fe6d028 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorAliceNullVideo.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorAliceNullVideo.java @@ -74,14 +74,14 @@ public SectorAliceNullVideo(@Nonnull CdSector cdSector) { if (_header.lngMagic != ALICE_VIDEO_SECTOR_MAGIC) return; - if (!_header.isChunkNumberStandard()) + if (!_header.hasStandardChunkNumber()) return; if (_header.iChunksInThisFrame < 3) return; // null frames between movies have a frame number of 0xFFFF // the high bit signifies the end of a video - if (!_header.isFrameNumberStandard()) + if (!_header.hasStandardFrameNumber()) return; // make sure all 16 bytes are zero diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorClaimToStrVideoSector.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorClaimToStrVideoSector.java index e12e4e2..fa09971 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorClaimToStrVideoSector.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorClaimToStrVideoSector.java @@ -40,6 +40,7 @@ import java.io.IOException; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; @@ -51,8 +52,10 @@ public class SectorClaimToStrVideoSector extends SectorClaimSystem.SectorClaimer public interface Listener { void feedSector(@Nonnull ISelfDemuxingVideoSector vidSector, - @Nonnull ILocalizedLogger log); - void endOfSectors(@Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; + void endOfSectors(@Nonnull ILocalizedLogger log) + throws LoggedFailure; } @CheckForNull @@ -70,7 +73,7 @@ public void setListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { if (cs.isClaimed()) return; @@ -79,12 +82,23 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, if (vidSector != null && _listener != null && sectorIsInRange(cs.getSector().getSectorIndexFromStart())) { - _listener.feedSector(vidSector, log); + try { + _listener.feedSector(vidSector, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } } } - public void endOfSectors(@Nonnull ILocalizedLogger log) { - if (_listener != null) - _listener.endOfSectors(log); + public void endOfSectors(@Nonnull ILocalizedLogger log) + throws SectorClaimSystem.ClaimerFailure + { + if (_listener != null) { + try { + _listener.endOfSectors(log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } + } } } diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorFF7Video.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorFF7Video.java index b70462d..0dab7a4 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorFF7Video.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorFF7Video.java @@ -77,9 +77,9 @@ public SectorFF7Video(@Nonnull CdSector cdSector) { return; if (_header.lngMagic != SectorStrVideo.VIDEO_SECTOR_MAGIC) return; - if (!_header.isChunkNumberStandard()) return; + if (!_header.hasStandardChunkNumber()) return; if (_header.iChunksInThisFrame < 6 || _header.iChunksInThisFrame > 10) return; - if (!_header.isFrameNumberStandard()) return; + if (!_header.hasStandardFrameNumber()) return; // this block is unfortunately necessary to prevent false-positives with Lain sectors if (_header.iUsedDemuxedSize < 2500 || _header.iUsedDemuxedSize > 21000) return; _iWidth = cdSector.readSInt16LE(16); diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorIkiVideo.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorIkiVideo.java index d3f29fd..d9b02ab 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorIkiVideo.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorIkiVideo.java @@ -45,7 +45,7 @@ import jpsxdec.modules.video.sectorbased.SectorAbstractVideo; import jpsxdec.modules.video.sectorbased.VideoSectorCommon16byteHeader; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_Iki; -import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; +import jpsxdec.psxvideo.mdec.Calc; import jpsxdec.util.IO; @@ -78,10 +78,10 @@ public SectorIkiVideo(@Nonnull CdSector cdSector) { return; if (_header.lngMagic != SectorStrVideo.VIDEO_SECTOR_MAGIC) return; - if (!_header.isChunkNumberStandard()) return; - if (!_header.isChunksInFrameStandard()) return; - if (!_header.isFrameNumberStandard()) return; - if (!_header.isUsedDemuxSizeStandard()) return; + if (!_header.hasStandardChunkNumber()) return; + if (!_header.hasStandardChunksInFrame()) return; + if (!_header.hasStandardFrameNumber()) return; + if (!_header.hasStandardUsedDemuxSize()) return; _iWidth = cdSector.readSInt16LE(16); if (_iWidth < 1) return; _iHeight = cdSector.readSInt16LE(18); @@ -160,7 +160,7 @@ public void replaceVideoSectorHeader(@Nonnull byte[] abNewDemuxData, int iNewUse IO.writeInt32LE(abCurrentVidSectorHeader, 12, iDemuxSizeForHeader); IO.writeInt16LE(abCurrentVidSectorHeader, 20, - BitStreamUncompressor_STRv2.calculateHalfCeiling32(iNewMdecCodeCount)); + Calc.calculateHalfCeiling32(iNewMdecCodeCount)); } } diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorLainVideo.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorLainVideo.java index 3a23594..72cd617 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorLainVideo.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorLainVideo.java @@ -78,7 +78,7 @@ public SectorLainVideo(@Nonnull CdSector cdSector) { if (_header.lngMagic != SectorStrVideo.VIDEO_SECTOR_MAGIC) return; - if (!_header.isChunkNumberStandard()) return; + if (!_header.hasStandardChunkNumber()) return; // this detail helps to avoid matching FF7 video sectors if (_header.iChunksInThisFrame != 9 && _header.iChunksInThisFrame != 10) return; if (_header.iFrameNumber < 1) return; diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorReBoot.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorReBoot.java new file mode 100644 index 0000000..02703ac --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorReBoot.java @@ -0,0 +1,154 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.modules.strvideo; + +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdSector; +import jpsxdec.cdreaders.CdSectorXaSubHeader; +import jpsxdec.cdreaders.CdSectorXaSubHeader.SubMode; +import jpsxdec.modules.video.sectorbased.SectorAbstractVideo; +import jpsxdec.modules.video.sectorbased.VideoSectorCommon16byteHeader; +import jpsxdec.util.IO; + + +/** ReBoot video sector variant. + * Thanks to XBrav for doing the research to make this possible. */ +public class SectorReBoot extends SectorAbstractVideo { + + @Nonnull + private final VideoSectorCommon16byteHeader _header; + private int _iWidth; // 16 [2 bytes] + private int _iHeight; // 18 [2 bytes] + // zeroes, in place of // 20 [2 bytes] + // RunLengthCodeCount + /** A number a little larger than the number of frames in the video. */ + private int _iMoreThanFrameCount; // 22 [2 bytes] + // zeroes, in place of // 24 [4 bytes] + // QuantizationScale + // Version + // zeroes // 28 [4 bytes] + // 32 TOTAL + + @Override + public int getVideoSectorHeaderSize() { return 32; } + + public SectorReBoot(@Nonnull CdSector cdSector) { + super(cdSector); + _header = new VideoSectorCommon16byteHeader(cdSector); + if (isSuperInvalidElseReset()) return; + + CdSectorXaSubHeader sh = cdSector.getSubHeader(); + if (sh != null) { + // only if it has a sector header should we check if it reports REALTIME | VIDEO + if (sh.getSubMode().toByte() != (SubMode.MASK_REAL_TIME | SubMode.MASK_VIDEO)) + return; + } + + if (_header.lngMagic != SectorStrVideo.VIDEO_SECTOR_MAGIC) return; + if (!_header.hasStandardChunkNumber()) return; + if (!_header.hasStandardChunksInFrame()) return; + if (!_header.hasStandardFrameNumber()) return; + if (!_header.hasStandardUsedDemuxSize()) return; + // some non-video sectors look a little like video sectors but with 1x1 dimensions + _iWidth = cdSector.readSInt16LE(16); + if (_iWidth < 2) return; + _iHeight = cdSector.readSInt16LE(18); + if (_iHeight < 2) return; + int iZeroes = cdSector.readUInt16LE(20); + if (iZeroes != 0) return; + _iMoreThanFrameCount = cdSector.readUInt16LE(22); + if (_iMoreThanFrameCount < 1) return; + for (int i = 0; i < 8; i++) { + if (cdSector.readUserDataByte(24 + i) != 0) return; + } + setProbability(100); + } + + // .. Public methods ................................................... + + public @Nonnull String getTypeName() { + return "ReBoot video"; + } + + public String toString() { + return String.format("%s %s frame:%d chunk:%d/%d %dx%d " + + "{demux frame size=%d %d>frame count}", + getTypeName(), + super.cdToString(), + _header.iFrameNumber, + _header.iChunkNumber, + _header.iChunksInThisFrame, + _iWidth, + _iHeight, + _header.iUsedDemuxedSize, + _iMoreThanFrameCount + ); + } + + public int getChunkNumber() { + return _header.iChunkNumber; + } + + public int getChunksInFrame() { + return _header.iChunksInThisFrame; + } + + public int getHeaderFrameNumber() { + return _header.iFrameNumber; + } + + public int getHeight() { + return _iHeight; + } + + public int getWidth() { + return _iWidth; + } + + public void replaceVideoSectorHeader(@Nonnull byte[] abNewDemuxData, int iNewUsedSize, + int iNewMdecCodeCount, @Nonnull byte[] abCurrentVidSectorHeader) + { + // Most values are 0 in the header + + // Qscale is not required, so I guess no reason to restrict the frame type + + int iDemuxSizeForHeader = (iNewUsedSize + 3) & ~3; + IO.writeInt32LE(abCurrentVidSectorHeader, 12, iDemuxSizeForHeader); + } +} + diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/SectorStrVideo.java b/jpsxdec/src/jpsxdec/modules/strvideo/SectorStrVideo.java index 9eff804..d572ec8 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/SectorStrVideo.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/SectorStrVideo.java @@ -47,6 +47,7 @@ import jpsxdec.modules.video.sectorbased.VideoSectorCommon16byteHeader; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv3; +import jpsxdec.psxvideo.mdec.Calc; import jpsxdec.util.IO; @@ -90,12 +91,12 @@ public SectorStrVideo(@Nonnull CdSector cdSector) { } if (_header.lngMagic != VIDEO_SECTOR_MAGIC) return; - if (!_header.isChunkNumberStandard()) return; - if (!_header.isChunksInFrameStandard()) return; + if (!_header.hasStandardChunkNumber()) return; + if (!_header.hasStandardChunksInFrame()) return; // normal STR sectors have frame number starting at 1 // but this STR sector also covers similar sectors that may not follow that - if (!_header.isFrameNumberStandard()) return; - if (!_header.isUsedDemuxSizeStandard()) return; + if (!_header.hasStandardFrameNumber()) return; + if (!_header.hasStandardUsedDemuxSize()) return; _iWidth = cdSector.readSInt16LE(16); if (_iWidth < 1) return; _iHeight = cdSector.readSInt16LE(18); @@ -180,11 +181,11 @@ public static void replaceStrVideoSectorHeader(@Nonnull byte[] abNewDemuxData, i BitStreamUncompressor_STRv2.StrV2Header v2Header = new BitStreamUncompressor_STRv2.StrV2Header(abNewDemuxData, iNewUsedSize); if (v2Header.isValid()) { - iQscale = v2Header.getQscale(); + iQscale = v2Header.getQuantizationScale(); } else { BitStreamUncompressor_STRv3.StrV3Header v3Header = new BitStreamUncompressor_STRv3.StrV3Header(abNewDemuxData, iNewUsedSize); if (v3Header.isValid()) { - iQscale = v3Header.getQscale(); + iQscale = v3Header.getQuantizationScale(); } else { throw new LocalizedIncompatibleException(I.REPLACE_FRAME_TYPE_NOT_V2_V3()); } @@ -194,7 +195,7 @@ public static void replaceStrVideoSectorHeader(@Nonnull byte[] abNewDemuxData, i IO.writeInt32LE(abCurrentVidSectorHeader, 12, iDemuxSizeForHeader); IO.writeInt16LE(abCurrentVidSectorHeader, 20, - BitStreamUncompressor_STRv2.calculateHalfCeiling32(iNewMdecCodeCount)); + Calc.calculateHalfCeiling32(iNewMdecCodeCount)); IO.writeInt16LE(abCurrentVidSectorHeader, 24, (short)(iQscale)); } diff --git a/jpsxdec/src/jpsxdec/modules/strvideo/StrVideoSectorToDemuxedStrFrame.java b/jpsxdec/src/jpsxdec/modules/strvideo/StrVideoSectorToDemuxedStrFrame.java index 1000210..86b6b05 100644 --- a/jpsxdec/src/jpsxdec/modules/strvideo/StrVideoSectorToDemuxedStrFrame.java +++ b/jpsxdec/src/jpsxdec/modules/strvideo/StrVideoSectorToDemuxedStrFrame.java @@ -39,6 +39,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.video.sectorbased.DemuxedFrameWithNumberAndDims; import jpsxdec.modules.video.sectorbased.ISelfDemuxingVideoSector; @@ -66,6 +67,7 @@ public void setListener(@CheckForNull DemuxedFrameWithNumberAndDims.Listener lis public void feedSector(@Nonnull ISelfDemuxingVideoSector vidSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure { if (_currentFrame != null && !_currentFrame.addSectorIfPartOfFrame(vidSector)) endFrame(log); @@ -85,15 +87,18 @@ public void feedSector(@Nonnull ISelfDemuxingVideoSector vidSector, // _listener.endVideo(); } - private void endFrame(@Nonnull ILocalizedLogger log) { + private void endFrame(@Nonnull ILocalizedLogger log) throws LoggedFailure { if (_currentFrame == null) return; - if (_listener != null) - _listener.frameComplete(_currentFrame.finishFrame(log), log); + if (_listener != null) { + DemuxedFrameWithNumberAndDims frame = _currentFrame.finishFrame(log); + if (frame != null) + _listener.frameComplete(frame, log); + } _currentFrame = null; } - public void endOfSectors(@Nonnull ILocalizedLogger log) { + public void endOfSectors(@Nonnull ILocalizedLogger log) throws LoggedFailure { endFrame(log); if (_listener != null) _listener.endOfSectors(log); diff --git a/jpsxdec/src/jpsxdec/modules/tim/TimSaverBuilder.java b/jpsxdec/src/jpsxdec/modules/tim/TimSaverBuilder.java index 0ff2a73..df08271 100644 --- a/jpsxdec/src/jpsxdec/modules/tim/TimSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/tim/TimSaverBuilder.java @@ -65,6 +65,7 @@ import jpsxdec.i18n.UnlocalizedMessage; import jpsxdec.i18n.exception.LocalizedFileNotFoundException; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.tim.Tim; import jpsxdec.util.ArgParser; @@ -105,6 +106,7 @@ private TimSaveFormat(@Nonnull JavaImageFormat eJavaFmt) { return _javaFmt; } + @Override public String toString() { if (_javaFmt == null) return "tim"; @@ -133,10 +135,9 @@ public String getExtension() { for (JavaImageFormat jif : availableFormats) { if (jif.isAvailable()) { if (jif.hasTrueColor()) { + TRUE_COLOR_FORMAT_LIST.add(new TimSaveFormat(jif)); if (jif.hasAlpha()) TRUE_COLOR_ALPHA_FORMAT_LIST.add(new TimSaveFormat(jif)); - else - TRUE_COLOR_FORMAT_LIST.add(new TimSaveFormat(jif)); } PALETTE_FORMAT_LIST.add(new TimSaveFormat(jif)); } @@ -294,7 +295,7 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb if (timpalettes.value != null) { boolean[] ablnNewValues = parseNumberListRange(timpalettes.value, getPaletteCount()); if (ablnNewValues == null) { - fbs.printlnWarn(I.CMD_TIM_PALETTE_LIST_INVALID(timpalettes.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(timpalettes.value, "-pal")); } else { System.arraycopy(ablnNewValues, 0, _ablnSavePalette, 0, getPaletteCount()); } @@ -303,7 +304,7 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb if (format.value != null) { TimSaveFormat fmt = fromCmdLine(format.value); if (fmt == null) { - fbs.printlnWarn(I.CMD_TIM_SAVE_FORMAT_INVALID(format.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(format.value, "-if,-imgfmt")); } else { setImageFormat(fmt); } @@ -351,9 +352,10 @@ public void printHelp(@Nonnull FeedbackStream fbs) { tfb.write(fbs.getUnderlyingStream()); } - public void printSelectedOptions(@Nonnull FeedbackStream fbs) { - fbs.println(I.CMD_TIM_SAVE_FORMAT(getImageFormat().getExtension())); - fbs.println(getOutputFilesSummary()); + public void printSelectedOptions(@Nonnull + ILocalizedLogger log) { + log.log(Level.INFO, I.CMD_TIM_SAVE_FORMAT(getImageFormat().getExtension())); + log.log(Level.INFO, getOutputFilesSummary()); } private @Nonnull String makeTimFileName() { @@ -375,6 +377,7 @@ public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File directory) throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); if (getImageFormat() == TIM) { startSaveTim(pl, directory, makeTimFileName()); } else { diff --git a/jpsxdec/src/jpsxdec/modules/video/DiscItemVideoStream.java b/jpsxdec/src/jpsxdec/modules/video/DiscItemVideoStream.java index db3f385..4ef00f9 100644 --- a/jpsxdec/src/jpsxdec/modules/video/DiscItemVideoStream.java +++ b/jpsxdec/src/jpsxdec/modules/video/DiscItemVideoStream.java @@ -57,6 +57,7 @@ import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; import jpsxdec.psxvideo.encode.ParsedMdecImage; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.Fraction; import jpsxdec.util.TaskCanceledException; @@ -133,6 +134,17 @@ final public int getFrameCount() { /** Returns the sector on the disc where the video should start playing. */ abstract public int getAbsolutePresentationStartSector(); + /** Returns if the raw video frame data (the bitstream) can be identified + * and decoded independent of any extra information. + * Nearly all videos do have bitstreams that can be identified and decoded + * all on their own (except for the frame dimensions usually). + * A few games however use very different bitstream formats that, on their + * own, would be impossible to decode. They need some additional + * contextual information to do so. + * The video saver uses this information to determine if the bitstream + * format (.bs) can be used as an output format. */ + abstract public boolean hasIndependentBitstream(); + /** Returns the approximate duration of the video in seconds. * Intended for use with video playback progress bar. */ abstract public double getApproxDuration(); @@ -151,12 +163,16 @@ public void frameComplete(IDemuxedFrame frame) { ps.println(" Available demux size: " + frame.getDemuxSize()); frame.printSectors(ps); // ideally would be indented by 4 - byte[] abBitStream = frame.copyDemuxData(); try { - BitStreamUncompressor uncompressor = BitStreamUncompressor.identifyUncompressor(abBitStream, frame.getDemuxSize()); - ParsedMdecImage parsed = new ParsedMdecImage(uncompressor, getWidth(), getHeight()); - uncompressor.skipPaddingBits(); - ps.println(" Bitstream info: " + uncompressor); + MdecInputStream mis = frame.getCustomFrameMdecStream(); + if (mis == null) { + byte[] abBitStream = frame.copyDemuxData(); + BitStreamUncompressor uncompressor = BitStreamUncompressor.identifyUncompressor(abBitStream, frame.getDemuxSize()); + uncompressor.skipPaddingBits(); + mis = uncompressor; + } + ParsedMdecImage parsed = new ParsedMdecImage(mis, getWidth(), getHeight()); + ps.println(" Frame data info: " + mis); if (blnMore) { int iMbWidth = Calc.macroblockDim(getWidth()), iMbHeight = Calc.macroblockDim(getHeight()); diff --git a/jpsxdec/src/jpsxdec/modules/video/IDemuxedFrame.java b/jpsxdec/src/jpsxdec/modules/video/IDemuxedFrame.java index dce130d..a15f6f0 100644 --- a/jpsxdec/src/jpsxdec/modules/video/IDemuxedFrame.java +++ b/jpsxdec/src/jpsxdec/modules/video/IDemuxedFrame.java @@ -38,18 +38,20 @@ package jpsxdec.modules.video; import java.io.PrintStream; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.cdreaders.CdFileSectorReader; import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.Fraction; /** Universal demuxed video frame. */ public interface IDemuxedFrame { public interface Listener { - void frameComplete(@Nonnull IDemuxedFrame frame); + void frameComplete(@Nonnull IDemuxedFrame frame) throws LoggedFailure; } int getWidth(); @@ -71,12 +73,13 @@ public interface Listener { /** Size of the demuxed frame. */ int getDemuxSize(); - /** Returns the contiguous demux copied into a buffer. If the supplied - * buffer is not null and is big enough to fit the demuxed data, it is used, - * otherwise a new buffer is created and returned. - * @return the byte[] */ + /** Returns the contiguous demux copied into a buffer. */ @Nonnull byte[] copyDemuxData(); + /** The demux data my not be able to be converted to an mdec stream on its + * own. This can provide a direct mdec stream instead. */ + @CheckForNull MdecInputStream getCustomFrameMdecStream(); + void printSectors(@Nonnull PrintStream ps); void writeToSectors(@Nonnull byte[] abNewDemux, diff --git a/jpsxdec/src/jpsxdec/modules/video/framenumber/FrameNumberNumber.java b/jpsxdec/src/jpsxdec/modules/video/framenumber/FrameNumberNumber.java index 5fb7d82..d5a2517 100644 --- a/jpsxdec/src/jpsxdec/modules/video/framenumber/FrameNumberNumber.java +++ b/jpsxdec/src/jpsxdec/modules/video/framenumber/FrameNumberNumber.java @@ -189,6 +189,14 @@ public void addNumber(int iFrameValue) { } } + public int getStartFrameValue() { + return _iStartFrameValue; + } + + public int getLastFrameValue() { + return _iEndFrameValue; + } + public @Nonnull Format makeFormat() { return new Format(_iStartFrameValue, _iEndFrameValue, _iDuplicate, _iDuplicateMax); diff --git a/jpsxdec/src/jpsxdec/modules/video/framenumber/HeaderFrameNumber.java b/jpsxdec/src/jpsxdec/modules/video/framenumber/HeaderFrameNumber.java index 0cf27cd..04bafaa 100644 --- a/jpsxdec/src/jpsxdec/modules/video/framenumber/HeaderFrameNumber.java +++ b/jpsxdec/src/jpsxdec/modules/video/framenumber/HeaderFrameNumber.java @@ -76,13 +76,21 @@ public void addHeaderFrameNumber(int iHeaderFrameNumber) { _headerNumberBuilder.addNumber(iHeaderFrameNumber); } + public int getStartFrameNumber() { + return _headerNumberBuilder.getStartFrameValue(); + } + + public int getLastFrameNumber() { + return _headerNumberBuilder.getLastFrameValue(); + } + public @Nonnull Format makeFormat() { return new Format(_headerNumberBuilder.makeFormat()); } } - private static final String HEADER_FORMAT_KEY = "Header Frames"; + public static final String HEADER_FORMAT_KEY = "Header Frames"; @Nonnull private final FrameNumberNumber.Format _headerNumberFormat; diff --git a/jpsxdec/src/jpsxdec/modules/video/framenumber/IndexSectorFrameNumber.java b/jpsxdec/src/jpsxdec/modules/video/framenumber/IndexSectorFrameNumber.java index c5f842c..c754fde 100644 --- a/jpsxdec/src/jpsxdec/modules/video/framenumber/IndexSectorFrameNumber.java +++ b/jpsxdec/src/jpsxdec/modules/video/framenumber/IndexSectorFrameNumber.java @@ -182,4 +182,18 @@ private static void warnSectorFrameNumberIssues(@Nonnull FrameNumberNumber secto return fn; } } + + public static @Nonnull IFrameNumberFormatter makeSimpleFormatter( + int iFrameCount, + int iSectorMinValue, int iSectorMaxValue, int iSectorDuplicateMax) + { + // set the end duplicate = max duplicate for simplicity + FrameNumberNumber.Format snf = new FrameNumberNumber.Format( + iSectorMinValue, iSectorMaxValue, iSectorDuplicateMax, iSectorDuplicateMax); + FrameNumberNumber.Formatter sf = new FrameNumberNumber.Formatter(snf); + + IndexSectorFrameNumber.Formatter sect = new IndexSectorFrameNumber.Formatter(iFrameCount, sf); + return sect; + } + } diff --git a/jpsxdec/src/jpsxdec/modules/video/packetbased/DiscItemPacketBasedVideoStream.java b/jpsxdec/src/jpsxdec/modules/video/packetbased/DiscItemPacketBasedVideoStream.java new file mode 100644 index 0000000..ebddcb3 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/video/packetbased/DiscItemPacketBasedVideoStream.java @@ -0,0 +1,138 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2012-2019 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.modules.video.packetbased; + +import java.util.Date; +import javax.annotation.Nonnull; +import jpsxdec.cdreaders.CdFileSectorReader; +import jpsxdec.discitems.DiscItemSaverBuilder; +import jpsxdec.discitems.SerializedDiscItem; +import jpsxdec.i18n.I; +import jpsxdec.i18n.ILocalizedMessage; +import jpsxdec.i18n.exception.LocalizedDeserializationFail; +import jpsxdec.modules.player.MediaPlayer; +import jpsxdec.modules.video.Dimensions; +import jpsxdec.modules.video.DiscItemVideoStream; +import jpsxdec.modules.video.ISectorClaimToDemuxedFrame; +import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; +import jpsxdec.util.Misc; +import jpsxdec.util.player.PlayController; + + +public abstract class DiscItemPacketBasedVideoStream extends DiscItemVideoStream { + + private static final String SOUND_UNIT_COUNT_KEY = "Sound unit count"; + private final int _iSoundUnitCount; + + public DiscItemPacketBasedVideoStream(@Nonnull CdFileSectorReader cd, + int iStartSector, int iEndSector, + @Nonnull Dimensions dim, + @Nonnull IndexSectorFrameNumber.Format sectorIndexFrameNumberFormat, + int iSoundUnitCount) + { + super(cd, iStartSector, iEndSector, dim, sectorIndexFrameNumberFormat); + _iSoundUnitCount = iSoundUnitCount; + } + + public DiscItemPacketBasedVideoStream(@Nonnull CdFileSectorReader cd, @Nonnull SerializedDiscItem fields) + throws LocalizedDeserializationFail + { + super(cd, fields); + _iSoundUnitCount = fields.getInt(SOUND_UNIT_COUNT_KEY); + } + + @Override + public @Nonnull SerializedDiscItem serialize() { + SerializedDiscItem serial = super.serialize(); + serial.addNumber(SOUND_UNIT_COUNT_KEY, _iSoundUnitCount); + return serial; + } + + final public boolean hasAudio() { + return _iSoundUnitCount > 0; + } + + @Override + final public int getDiscSpeed() { + return 2; // this doesn't really matter + } + + @Override + final public @Nonnull ILocalizedMessage getInterestingDescription() { + int iFrames = getFrameCount(); + double dblFps = getPacketBasedFpsInterestingDescription(); + Date secs = Misc.dateFromSeconds(Math.max(iFrames / (int)Math.round(dblFps), 1)); + if (hasAudio()) + return I.GUI_PACKET_BASED_VID_DETAILS_WITH_AUDIO(getWidth() ,getHeight(), iFrames, dblFps, secs, getAudioSampleFramesPerSecond()); + else + return I.GUI_PACKET_BASED_VID_DETAILS(getWidth() ,getHeight(), iFrames, dblFps, secs); + } + + abstract protected double getPacketBasedFpsInterestingDescription(); + + @Override + final public int getAbsolutePresentationStartSector() { + return getStartSector(); + } + + abstract public @Nonnull SectorClaimToAudioAndFrame makeAudioVideoDemuxer(double dblVolume); + abstract public int getAudioSampleFramesPerSecond(); + + @Override + final public ISectorClaimToDemuxedFrame makeDemuxer() { + return makeAudioVideoDemuxer(1.0); + } + + @Override + final public DiscItemSaverBuilder makeSaverBuilder() { + return new PacketBasedVideoSaverBuilder(this); + } + + @Override + final public @Nonnull PlayController makePlayController() { + SectorClaimToAudioAndFrame demuxer = makeAudioVideoDemuxer(1.0); + MediaPlayer mp; + if (hasAudio()) + mp = new MediaPlayer(this, demuxer, demuxer, getStartSector(), getEndSector()); + else + mp = new MediaPlayer(this, demuxer, getStartSector(), getEndSector()); + return mp.getPlayController(); + } + + +} diff --git a/jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusader.java b/jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilder.java similarity index 79% rename from jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusader.java rename to jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilder.java index 2525cd9..b5b6831 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusader.java +++ b/jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilder.java @@ -35,10 +35,11 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package jpsxdec.modules.crusader; +package jpsxdec.modules.video.packetbased; import argparser.BooleanHolder; import java.io.File; +import java.util.logging.Level; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.discitems.DiscItemSaverBuilder; @@ -47,19 +48,20 @@ import jpsxdec.i18n.I; import jpsxdec.i18n.TabularFeedback; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; +import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; import jpsxdec.modules.video.save.VideoSaver; import jpsxdec.modules.video.save.VideoSaverBuilder; import jpsxdec.util.ArgParser; import jpsxdec.util.TaskCanceledException; -/** Extends {@link VideoSaverBuilder} with Crusader specific settings. */ -public class VideoSaverBuilderCrusader extends VideoSaverBuilder { +public class PacketBasedVideoSaverBuilder extends VideoSaverBuilder { @Nonnull - private final DiscItemCrusader _sourceVidItem; + private final DiscItemPacketBasedVideoStream _sourceVidItem; - public VideoSaverBuilderCrusader(@Nonnull DiscItemCrusader vidItem) { + public PacketBasedVideoSaverBuilder(@Nonnull DiscItemPacketBasedVideoStream vidItem) { super(vidItem); _sourceVidItem = vidItem; } @@ -67,8 +69,8 @@ public VideoSaverBuilderCrusader(@Nonnull DiscItemCrusader vidItem) { @Override public boolean copySettingsTo(@Nonnull DiscItemSaverBuilder otherBuilder) { if (super.copySettingsTo(otherBuilder)) { - if (otherBuilder instanceof VideoSaverBuilderCrusader){ - VideoSaverBuilderCrusader other = (VideoSaverBuilderCrusader) otherBuilder; + if (otherBuilder instanceof PacketBasedVideoSaverBuilder){ + PacketBasedVideoSaverBuilder other = (PacketBasedVideoSaverBuilder) otherBuilder; other.setSavingAudio(getSavingAudio()); } return true; @@ -78,7 +80,7 @@ public boolean copySettingsTo(@Nonnull DiscItemSaverBuilder otherBuilder) { } public @Nonnull DiscItemSaverBuilderGui getOptionPane() { - return new VideoSaverBuilderCrusaderGui(this); + return new PacketBasedVideoSaverBuilderGui(this); } // ......................................................................... @@ -91,11 +93,11 @@ public boolean getAudioVolume_enabled() { // ......................................................................... public boolean getSavingAudio_enabled() { - return getVideoFormat().isAvi() && getSaveStartFrame() == null; + return hasAudio() && getVideoFormat().isAvi() && getSaveStartFrame() == null; } public boolean hasAudio() { - return true; + return _sourceVidItem.hasAudio(); } private boolean _blnSavingAudio = true; @@ -140,28 +142,26 @@ protected void makeHelpTable(@Nonnull TabularFeedback tfb) { } @Override - protected void printSelectedAudioOptions(@Nonnull FeedbackStream fbs) { - fbs.println(I.EMBEDDED_CRUSADER_AUDIO_HZ(CrusaderPacketToFrameAndAudio.CRUSADER_SAMPLES_PER_SECOND)); + protected void printSelectedAudioOptions(@Nonnull ILocalizedLogger log) { + log.log(Level.INFO, I.CMD_EMBEDDED_PACKET_BASED_AUDIO_HZ(_sourceVidItem.getAudioSampleFramesPerSecond())); } public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File directory) throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); - DiscItemCrusader.Demuxer av = _sourceVidItem.makeDemuxer(getAudioVolume()); - DiscItemCrusader.Demuxer ad; + SectorClaimToAudioAndFrame vid = _sourceVidItem.makeAudioVideoDemuxer(getAudioVolume()); + ISectorAudioDecoder aud; if (getSavingAudio()) { - // TODO the saver will call attach twice, - // which works since the 2nd time will simply overwrite the first time - // it's just kinda messy - ad = av; + aud = vid; } else { - ad = null; + aud = null; } - VideoSaver vs = new VideoSaver(_sourceVidItem, this, thisGeneratedFileListener, directory); - vs.save(pl, av, ad); + VideoSaver vs = new VideoSaver(_sourceVidItem, this, thisGeneratedFileListener, directory, pl, vid, aud); + vs.save(pl); } } diff --git a/jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusaderGui.java b/jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilderGui.java similarity index 81% rename from jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusaderGui.java rename to jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilderGui.java index 354f3fe..cf7e0bf 100644 --- a/jpsxdec/src/jpsxdec/modules/crusader/VideoSaverBuilderCrusaderGui.java +++ b/jpsxdec/src/jpsxdec/modules/video/packetbased/PacketBasedVideoSaverBuilderGui.java @@ -35,7 +35,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package jpsxdec.modules.crusader; +package jpsxdec.modules.video.packetbased; import java.awt.BorderLayout; import javax.annotation.Nonnull; @@ -46,14 +46,14 @@ import jpsxdec.modules.video.save.VideoSaverPanel; -public class VideoSaverBuilderCrusaderGui extends DiscItemSaverBuilderGui { +public class PacketBasedVideoSaverBuilderGui extends DiscItemSaverBuilderGui { @Nonnull - private final CombinedBuilderListener _bl; + private final CombinedBuilderListener _bl; - public VideoSaverBuilderCrusaderGui(@Nonnull VideoSaverBuilderCrusader saverBuilder) { + public PacketBasedVideoSaverBuilderGui(@Nonnull PacketBasedVideoSaverBuilder saverBuilder) { super(new BorderLayout()); - _bl = new CombinedBuilderListener(saverBuilder); + _bl = new CombinedBuilderListener(saverBuilder); add(new PPanel(_bl), BorderLayout.NORTH); } @@ -62,9 +62,9 @@ public boolean useSaverBuilder(@Nonnull DiscItemSaverBuilder saverBuilder) { return _bl.changeSourceBuilder(saverBuilder); } - private static class PPanel extends VideoSaverPanel { + private static class PPanel extends VideoSaverPanel { - public PPanel(@Nonnull CombinedBuilderListener bl) { + public PPanel(@Nonnull CombinedBuilderListener bl) { super(bl); bl.addListeners(new SaveAudio()); } @@ -80,6 +80,10 @@ public void setSelected(boolean b) { public boolean isEnabled() { return _bl.getBuilder().getSavingAudio_enabled(); } + @Override + public boolean isVisibile() { + return _bl.getBuilder().hasAudio(); + } } } diff --git a/jpsxdec/src/jpsxdec/modules/video/packetbased/SectorClaimToAudioAndFrame.java b/jpsxdec/src/jpsxdec/modules/video/packetbased/SectorClaimToAudioAndFrame.java new file mode 100644 index 0000000..6b0d988 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/video/packetbased/SectorClaimToAudioAndFrame.java @@ -0,0 +1,47 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.video.packetbased; + +import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; +import jpsxdec.modules.video.ISectorClaimToDemuxedFrame; + + +public abstract class SectorClaimToAudioAndFrame + implements ISectorClaimToDemuxedFrame, ISectorAudioDecoder +{ +} diff --git a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrameFull.java b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrameFull.java index 160d24c..d7122f3 100644 --- a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrameFull.java +++ b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrameFull.java @@ -189,25 +189,27 @@ else if (abNewFrame.length > frame.getDemuxSize()) // for bs or mdec formats getFrameLookup().toString(), abNewFrame.length, frame.getDemuxSize())); // find out how many bytes and mdec codes are used by the new frame - BitStreamUncompressor verifyBsu; + BitStreamUncompressor verifiedBsu; try { + verifiedBsu = BitStreamUncompressor.identifyUncompressor(abNewFrame); // also verify it is the same bitstream type (for bs format) - verifyBsu = bsu.getType().makeNew(abNewFrame); + if (!verifiedBsu.getClass().equals(bsu.getClass())) + throw new BinaryDataNotRecognized(); } catch (BinaryDataNotRecognized ex) { throw new LoggedFailure(log, Level.SEVERE, I.REPLACE_BITSTREAM_MISMATCH(_imageFile), ex); } try { - verifyBsu.skipMacroBlocks(frame.getWidth(), frame.getHeight()); - verifyBsu.skipPaddingBits(); + verifiedBsu.skipMacroBlocks(frame.getWidth(), frame.getHeight()); + verifiedBsu.skipPaddingBits(); } catch (MdecException.EndOfStream ex) { throw new RuntimeException("Can't decode a frame we just encoded?", ex); } catch (MdecException.ReadCorruption ex) { throw new RuntimeException("Can't decode a frame we just encoded?", ex); } - int iUsedSize = ((verifyBsu.getBitPosition() + 15) / 16) * 2; // rounded up to nearest word - frame.writeToSectors(abNewFrame, iUsedSize, verifyBsu.getReadMdecCodeCount(), cd, log); + int iUsedSize = ((verifiedBsu.getBitPosition() + 15) / 16) * 2; // rounded up to nearest word + frame.writeToSectors(abNewFrame, iUsedSize, verifiedBsu.getReadMdecCodeCount(), cd, log); } private static byte[] readBitstreamFile(@Nonnull File imageFile, @Nonnull ILocalizedLogger log) diff --git a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFramePartial.java b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFramePartial.java index edf6364..c578943 100644 --- a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFramePartial.java +++ b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFramePartial.java @@ -63,7 +63,7 @@ import jpsxdec.psxvideo.encode.MdecEncoder; import jpsxdec.psxvideo.encode.ParsedMdecImage; import jpsxdec.psxvideo.encode.PsxYCbCrImage; -import jpsxdec.psxvideo.mdec.Ac0Cleaner; +import jpsxdec.psxvideo.mdec.Ac0Checker; import jpsxdec.psxvideo.mdec.Calc; import jpsxdec.psxvideo.mdec.MdecDecoder_double; import jpsxdec.psxvideo.mdec.MdecException; @@ -177,7 +177,8 @@ public void replace(@Nonnull IDemuxedFrame frame, @Nonnull CdFileSectorReader cd final int WIDTH = frame.getWidth(); final int HEIGHT = frame.getHeight(); if (newImg.getWidth() < WIDTH || newImg.getHeight() < HEIGHT) - throw new LoggedFailure(log, Level.SEVERE, I.REPLACE_FRAME_DIMENSIONS_TOO_SMALL()); + throw new LoggedFailure(log, Level.SEVERE, I.REPLACE_FRAME_DIMENSIONS_TOO_SMALL( + newImg.getWidth(), newImg.getHeight(), frame.getWidth(), frame.getHeight())); // 1. Parse original image byte[] abExistingFrame = frame.copyDemuxData(); @@ -191,7 +192,7 @@ public void replace(@Nonnull IDemuxedFrame frame, @Nonnull CdFileSectorReader cd MdecDecoder_double decoder = new MdecDecoder_double(new StephensIDCT(), WIDTH, HEIGHT); try { - ParsedMdecImage parsedOrig = new ParsedMdecImage(new Ac0Cleaner(bsu), WIDTH, HEIGHT); + ParsedMdecImage parsedOrig = new ParsedMdecImage(Ac0Checker.wrapWithChecker(bsu, true), WIDTH, HEIGHT); // 2. convert both to RGB // TODO: use best quality to decode, but same as encode @@ -206,7 +207,7 @@ public void replace(@Nonnull IDemuxedFrame frame, @Nonnull CdFileSectorReader cd // the bounding box and mask ArrayList diffMacblks = findDiffMacroblocks(origImg, newImg, log); if (diffMacblks.isEmpty()) { - log.log(Level.INFO, I.CMD_NO_DIFFERENCE_SKIPPING()); + log.log(Level.INFO, I.CMD_NO_DIFFERENCE_SKIPPING(getFrameLookup().toString())); return; } else if (diffMacblks.size() == Calc.macroblocks(WIDTH, HEIGHT)) { log.log(Level.WARNING, I.CMD_ENTIRE_FRAME_DIFFERENT()); diff --git a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrames.java b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrames.java index 3471be7..1794ee1 100644 --- a/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrames.java +++ b/jpsxdec/src/jpsxdec/modules/video/replace/ReplaceFrames.java @@ -69,7 +69,6 @@ import jpsxdec.modules.video.framenumber.FrameCompareIs; import jpsxdec.modules.video.framenumber.FrameNumber; import jpsxdec.util.IO; -import jpsxdec.util.IOException6; import jpsxdec.util.TaskCanceledException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -99,7 +98,7 @@ public XmlFileNotFoundException(FileNotFoundException cause) { } - public static class XmlReadException extends IOException6 { + public static class XmlReadException extends IOException { public XmlReadException(IOException cause) { super(cause); @@ -196,9 +195,9 @@ public void save(@Nonnull String sFile) throws IOException { StreamResult result = new StreamResult(new File(sFile)); transformer.transform(source, result); } catch (ParserConfigurationException ex) { - throw new IOException6(ex); + throw new IOException(ex); } catch (TransformerException ex) { - throw new IOException6(ex); + throw new IOException(ex); } } diff --git a/jpsxdec/src/jpsxdec/modules/video/save/AutowireVDP.java b/jpsxdec/src/jpsxdec/modules/video/save/AutowireVDP.java new file mode 100644 index 0000000..63f0921 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/video/save/AutowireVDP.java @@ -0,0 +1,302 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.video.save; + +import java.util.Arrays; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.sharedaudio.DecodedAudioPacket; +import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.ISectorClaimToDemuxedFrame; + +/** Automatically connects all video pipeline and other components. */ +public class AutowireVDP { + + // Expose these publicly for the saver to send data into + @CheckForNull + private VDP.IBitstreamListener _bitstreamListener; + public @CheckForNull VDP.IBitstreamListener getBitstreamListener() { + return _bitstreamListener; + } + @CheckForNull + private VDP.IMdecListener _mdecListener; + public @CheckForNull VDP.IMdecListener getMdecListener() { + return _mdecListener; + } + + // Expose this publicly to open/close the AVI + @CheckForNull + private VDP.ToAvi _toAvi; + public @CheckForNull VDP.ToAvi getAvi() { + return _toAvi; + } + + + // ========================================================================= + + public void autowire() throws IllegalStateException { + _bitstreamListener = chooseOnlyOne(_bitstream2File, _bitstream2Mdec); + _mdecListener = chooseOnlyOne(_mdec2Decoded, _mdec2File, _mdec2Jpeg, _mdec2MjpegAvi); + + if (_frame2Bitstream != null) { + _frame2Bitstream.setListener(_bitstreamListener); + _frame2Bitstream.setListener(_mdecListener); + } + + wireDecodedIntoMdec(); + wireMdecIntoBitstream(); + _toAvi = chooseOnlyOne(_decoded2JYuvAvi, _decoded2RgbAvi, _decoded2YuvAvi, _mdec2MjpegAvi); + wireGenFileListener(); + wireSectorClaim(); + wireAudioAndFrame(); + } + + private void wireGenFileListener() { + if (_generatedFileListener == null) + return; + if (_bitstream2File != null) + _bitstream2File.setGenFileListener(_generatedFileListener); + if (_decoded2JavaImage != null) + _decoded2JavaImage.setGenFileListener(_generatedFileListener); + if (_decoded2JYuvAvi != null) + _decoded2JYuvAvi.setGenFileListener(_generatedFileListener); + if (_decoded2RgbAvi != null) + _decoded2RgbAvi.setGenFileListener(_generatedFileListener); + if (_decoded2YuvAvi != null) + _decoded2YuvAvi.setGenFileListener(_generatedFileListener); + if (_mdec2File != null) + _mdec2File.setGenFileListener(_generatedFileListener); + if (_mdec2Jpeg != null) + _mdec2Jpeg.setGenFileListener(_generatedFileListener); + if (_mdec2MjpegAvi != null) + _mdec2MjpegAvi.setGenFileListener(_generatedFileListener); + } + + private void wireMdecIntoBitstream() { + VDP.IMdecListener mdecListener = chooseOnlyOne(_mdec2Decoded, _mdec2File, _mdec2Jpeg, _mdec2MjpegAvi); + if (_bitstream2Mdec == null) + return; + if (mdecListener != null) + _bitstream2Mdec.setMdecListener(mdecListener); + } + + private void wireDecodedIntoMdec() { + if (_decodedListener == null) + _decodedListener = chooseOnlyOne(_decoded2JYuvAvi, _decoded2JavaImage, _decoded2RgbAvi, _decoded2YuvAvi); + if (_decodedListener == null) + return; + if (_mdec2Decoded != null) + _mdec2Decoded.setDecoded(_decodedListener); + } + + private static @CheckForNull T chooseOnlyOne(T... elements) { + T nonNull = null; + for (T element : elements) { + if (element != null) { + if (nonNull != null) + throw new IllegalStateException("More than one set: " + Arrays.toString(elements)); + nonNull = element; + } + } + return nonNull; + } + + // ========================================================================= + // Sector claim, audio, video + + @CheckForNull + private SectorClaimSystem _sectorClaimSystem; + public void attachToSectorClaimer(@Nonnull SectorClaimSystem sectorClaimSystem) { + assertNull(_sectorClaimSystem); + _sectorClaimSystem = sectorClaimSystem; + } + + private void wireSectorClaim() { + if (_sectorClaimSystem == null) + return; + if (_sectorAudioDecoder != null) + _sectorAudioDecoder.attachToSectorClaimer(_sectorClaimSystem); + if (_sectorClaimToDemuxedFrame != null) + _sectorClaimToDemuxedFrame.attachToSectorClaimer(_sectorClaimSystem); + } + + @CheckForNull + private ISectorAudioDecoder _sectorAudioDecoder; + public void setAudioDecoder(@Nonnull ISectorAudioDecoder sectorAudioDecoder) { + assertNull(_sectorAudioDecoder); + _sectorAudioDecoder = sectorAudioDecoder; + } + @CheckForNull + private ISectorClaimToDemuxedFrame _sectorClaimToDemuxedFrame; + public void setMap(@Nonnull ISectorClaimToDemuxedFrame sectorClaimToDemuxedFrame) { + assertNull(_sectorClaimToDemuxedFrame); + _sectorClaimToDemuxedFrame = sectorClaimToDemuxedFrame; + } + + @CheckForNull + private IDemuxedFrame.Listener _frameListener; + public void setFrameListener(@Nonnull IDemuxedFrame.Listener frameListener) { + assertNull(_frameListener); + _frameListener = frameListener; + } + @CheckForNull + private Frame2Bitstream _frame2Bitstream; + public void setMap(@Nonnull Frame2Bitstream frame2Bitstream) { + setFrameListener(frame2Bitstream); + _frame2Bitstream = frame2Bitstream; + } + + @CheckForNull + private DecodedAudioPacket.Listener _audioListener; + public void setAudioPacketListener(@Nonnull DecodedAudioPacket.Listener audioListener) { + assertNull(_audioListener); + _audioListener = audioListener; + } + + private void wireAudioAndFrame() { + if (_sectorAudioDecoder != null && _audioListener != null) + _sectorAudioDecoder.setAudioListener(_audioListener); + if (_sectorClaimToDemuxedFrame != null && _frameListener != null) + _sectorClaimToDemuxedFrame.setFrameListener(_frameListener); + } + + // ========================================================================= + + @CheckForNull + private VDP.GeneratedFileListener _generatedFileListener; + public void setFileListener(@Nonnull VDP.GeneratedFileListener generatedFileListener) { + assertNull(_generatedFileListener); + _generatedFileListener = generatedFileListener; + } + + // ========================================================================= + // Bitstream + + + @CheckForNull + private VDP.Bitstream2File _bitstream2File; + public void setMap(@Nonnull VDP.Bitstream2File bitstream2File) { + assertNull(_bitstream2File); + _bitstream2File = bitstream2File; + } + + @CheckForNull + private VDP.Bitstream2Mdec _bitstream2Mdec; + public void setMap(@Nonnull VDP.Bitstream2Mdec bitstream2Mdec) { + assertNull(_bitstream2Mdec); + _bitstream2Mdec = bitstream2Mdec; + } + + // ========================================================================= + // decoded + + @CheckForNull + private VDP.Decoded2JavaImage _decoded2JavaImage; + public void setMap(@Nonnull VDP.Decoded2JavaImage decoded2JavaImage) { + assertNull(_decoded2JavaImage); + _decoded2JavaImage = decoded2JavaImage; + } + + // ========================================================================= + // MDEC + + @CheckForNull + private VDP.Mdec2Decoded _mdec2Decoded; + public void setMap(@Nonnull VDP.Mdec2Decoded mdec2Decoded) { + assertNull(_mdec2Decoded); + _mdec2Decoded = mdec2Decoded; + } + + @CheckForNull + private VDP.Mdec2File _mdec2File; + public void setMap(@Nonnull VDP.Mdec2File mdec2File) { + assertNull(_mdec2File); + _mdec2File = mdec2File; + } + + @CheckForNull + private VDP.Mdec2Jpeg _mdec2Jpeg; + public void setMap(@Nonnull VDP.Mdec2Jpeg mdec2Jpeg) { + assertNull(_mdec2Jpeg); + _mdec2Jpeg = mdec2Jpeg; + } + + // ========================================================================= + // AVI + + @CheckForNull + private VDP.IDecodedListener _decodedListener; + public void setDecodedListener(@Nonnull VDP.IDecodedListener decodedListener) { + assertNull(_decodedListener); + _decodedListener = decodedListener; + } + @CheckForNull + private VDP.Decoded2JYuvAvi _decoded2JYuvAvi; + public void setToAvi(@Nonnull VDP.Decoded2JYuvAvi decoded2JYuvAvi) { + assertNull(_decoded2JYuvAvi); + _decoded2JYuvAvi = decoded2JYuvAvi; + } + @CheckForNull + private VDP.Decoded2RgbAvi _decoded2RgbAvi; + public void setToAvi(@Nonnull VDP.Decoded2RgbAvi decoded2RgbAvi) { + assertNull(_decoded2RgbAvi); + _decoded2RgbAvi = decoded2RgbAvi; + } + @CheckForNull + private VDP.Decoded2YuvAvi _decoded2YuvAvi; + public void setToAvi(@Nonnull VDP.Decoded2YuvAvi decoded2YuvAvi) { + assertNull(_decoded2YuvAvi); + _decoded2YuvAvi = decoded2YuvAvi; + } + @CheckForNull + private VDP.Mdec2MjpegAvi _mdec2MjpegAvi; + public void setToAvi(@Nonnull VDP.Mdec2MjpegAvi mdec2MjpegAvi) { + assertNull(_mdec2MjpegAvi); + _mdec2MjpegAvi = mdec2MjpegAvi; + } + + // ========================================================================= + + private static void assertNull(Object o) { + if (o != null) + throw new IllegalStateException(o.getClass() + " was already set"); + } + +} diff --git a/jpsxdec/src/jpsxdec/modules/video/save/Frame2Bitstream.java b/jpsxdec/src/jpsxdec/modules/video/save/Frame2Bitstream.java new file mode 100644 index 0000000..1957ff2 --- /dev/null +++ b/jpsxdec/src/jpsxdec/modules/video/save/Frame2Bitstream.java @@ -0,0 +1,91 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.modules.video.save; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.modules.video.IDemuxedFrame; +import jpsxdec.modules.video.framenumber.FormattedFrameNumber; +import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; + +public class Frame2Bitstream implements IDemuxedFrame.Listener { + + @Nonnull + private final FrameNumber.Type _frameNumberType; + + @CheckForNull + private VDP.IBitstreamListener _bitstreamListener; + + @CheckForNull + private VDP.IMdecListener _mdecListener; + + public Frame2Bitstream(@Nonnull FrameNumber.Type frameNumberType) { + _frameNumberType = frameNumberType; + } + + final public void setListener(@CheckForNull VDP.IBitstreamListener bitstreamListener) { + _bitstreamListener = bitstreamListener; + } + + final public void setListener(@CheckForNull VDP.IMdecListener mdecListener) { + _mdecListener = mdecListener; + } + + final public @Nonnull FrameNumber.Type getFrameNumberType() { + return _frameNumberType; + } + + @Override + public void frameComplete(@Nonnull IDemuxedFrame frame) throws LoggedFailure { + FormattedFrameNumber ffn = frame.getFrame().getNumber(_frameNumberType); + MdecInputStream customStream = frame.getCustomFrameMdecStream(); + + if (customStream != null) { + if (_mdecListener == null) + throw new IllegalStateException("No mdec to write to"); + _mdecListener.mdec(customStream, ffn, frame.getPresentationSector()); + } else { + if (_bitstreamListener == null) + throw new IllegalStateException("No bitstream to write to"); + byte[] abBitstream = frame.copyDemuxData(); + _bitstreamListener.bitstream(abBitstream, frame.getDemuxSize(), ffn, frame.getPresentationSector()); + } + } +} diff --git a/jpsxdec/src/jpsxdec/modules/video/save/MdecDecodeQuality.java b/jpsxdec/src/jpsxdec/modules/video/save/MdecDecodeQuality.java index 1831751..08b5ef4 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/MdecDecodeQuality.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/MdecDecodeQuality.java @@ -42,7 +42,7 @@ import jpsxdec.i18n.I; import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.psxvideo.mdec.MdecDecoder; -import jpsxdec.psxvideo.mdec.MdecDecoder_double_interpolate; +import jpsxdec.psxvideo.mdec.MdecDecoder_double; import jpsxdec.psxvideo.mdec.MdecDecoder_int; import jpsxdec.psxvideo.mdec.idct.PsxMdecIDCT_double; import jpsxdec.psxvideo.mdec.idct.PsxMdecIDCT_int; @@ -55,9 +55,9 @@ public MdecDecoder makeDecoder(int iWidth, int iHeight) { return new MdecDecoder_int(new SimpleIDCT(), iWidth, iHeight); } }, - HIGH_PLUS(I.QUALITY_HIGH_DESCRIPTION(), I.QUALITY_HIGH_COMMAND()) { + HIGH(I.QUALITY_HIGH_DESCRIPTION(), I.QUALITY_HIGH_COMMAND()) { public MdecDecoder makeDecoder(int iWidth, int iHeight) { - return new MdecDecoder_double_interpolate(new PsxMdecIDCT_double(), iWidth, iHeight); + return new MdecDecoder_double(new PsxMdecIDCT_double(), iWidth, iHeight); } public boolean canUpsample() { return true; } }, diff --git a/jpsxdec/src/jpsxdec/modules/video/save/VDP.java b/jpsxdec/src/jpsxdec/modules/video/save/VDP.java index 0cbc5f1..068071e 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/VDP.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/VDP.java @@ -159,16 +159,19 @@ public void setGenFileListener(@CheckForNull GeneratedFileListener listener) { public static class Bitstream2Mdec implements IBitstreamListener { - @Nonnull - private final ILocalizedLogger _log; - @Nonnull - private final IMdecListener _listener; @CheckForNull - private BitStreamUncompressor.Type _uncompressorType; + private IMdecListener _listener; + @CheckForNull + private Class _uncompressorType; + public Bitstream2Mdec() { + } public Bitstream2Mdec(@Nonnull IMdecListener mdecListener) { _listener = mdecListener; - _log = _listener.getLog(); + } + + public void setMdecListener(@CheckForNull IMdecListener listener) { + _listener = listener; } public void bitstream(@Nonnull byte[] abBitstream, int iBitstreamSize, @@ -180,22 +183,25 @@ public void bitstream(@Nonnull byte[] abBitstream, int iBitstreamSize, BitStreamUncompressor uncompressor = BitStreamUncompressor.identifyUncompressor( abBitstream, iBitstreamSize); if (_uncompressorType != null) { - BitStreamUncompressor.Type newType = uncompressor.getType(); - if (_uncompressorType != newType) { + Class newType = uncompressor.getClass(); + if (!_uncompressorType.equals(newType)) { LOG.log(Level.WARNING, "Bitstream format changed from {0} to {1}", - new Object[]{_uncompressorType, newType}); + new Object[]{_uncompressorType.getSimpleName(), newType.getSimpleName()}); _uncompressorType = newType; } } else { - _uncompressorType = uncompressor.getType(); - LOG.log(Level.INFO, "Bitstream format identified {0}", - _uncompressorType); + _uncompressorType = uncompressor.getClass(); + LOG.log(Level.INFO, "Bitstream format identified {0}", + _uncompressorType.getSimpleName()); } - _listener.mdec(uncompressor, frameNumber, presentationSector); + if (_listener != null) + _listener.mdec(uncompressor, frameNumber, presentationSector); } catch (BinaryDataNotRecognized ex) { ILocalizedMessage msg = FrameMessage.UNABLE_TO_DETERMINE_FRAME_TYPE_FRM(frameNumber); - _log.log(Level.SEVERE, msg, ex); - _listener.error(msg, frameNumber, presentationSector); + if (_listener != null) { + _listener.getLog().log(Level.SEVERE, msg, ex); + _listener.error(msg, frameNumber, presentationSector); + } } } @@ -484,7 +490,7 @@ public void setGenFileListener(@CheckForNull GeneratedFileListener listener) { /** Most Avi will take Decoded as input, but MJPG will need Mdec as input, * so save the interface implementation for subclasses. */ - public static abstract class ToAvi implements Closeable { + public static abstract class ToAvi implements Closeable, DecodedAudioPacket.Listener { @Nonnull protected final File _outputFile; protected final int _iWidth, _iHeight; @@ -525,6 +531,10 @@ public ToAvi(@Nonnull File outputFile, int iWidth, int iHeight, return _outputFile; } + final public @CheckForNull AviWriter getAviWriter() { + return _writer; + } + abstract public void open() throws LocalizedFileNotFoundException, FileNotFoundException, IOException; @@ -565,9 +575,11 @@ final protected void prepForFrame(@CheckForNull FormattedFrameNumber frameNumber } else { while (iDupCount > 0) { // could happen with first frame if (_writer.getVideoFramesWritten() < 1) { // TODO: fix design so this isn't needed + _log.log(Level.INFO, I.WRITING_BLANK_FRAMES_TO_ALIGN_AV(1)); LOG.log(Level.INFO, "Writing blank frame for frame {0}", frameNumber); _writer.writeBlankFrame(); } else { + _log.log(Level.INFO, I.WRITING_DUP_FRAMES_TO_ALIGN_AV(1)); LOG.log(Level.INFO, "Writing dup frame for frame {0}", frameNumber); _writer.repeatPreviousFrame(); } @@ -576,7 +588,9 @@ final protected void prepForFrame(@CheckForNull FormattedFrameNumber frameNumber } } - final public void writeAudio(@Nonnull DecodedAudioPacket packet) throws LoggedFailure + final public void audioPacketComplete(@Nonnull DecodedAudioPacket packet, + @Nonnull ILocalizedLogger log) + throws LoggedFailure { if (_writer == null) throw new IllegalStateException("Avi writer is not open"); @@ -680,20 +694,27 @@ public void error(@Nonnull ILocalizedMessage errMsg, @CheckForNull FormattedFram } + /** Only supports videos with even dimensions. */ public static class Decoded2YuvAvi extends ToAvi implements IDecodedListener { @CheckForNull protected YCbCrImage _yuvImgBuff; @CheckForNull protected AviWriterYV12 _writerYuv; + /** @throws IllegalArgumentException if dimensions are not even */ public Decoded2YuvAvi(@Nonnull File outputFile, int iWidth, int iHeight, @Nonnull VideoSync vidSync, @Nonnull ILocalizedLogger log) { super(outputFile, iWidth, iHeight, vidSync, log); + if (((iWidth | iHeight) & 1) != 0) + throw new IllegalArgumentException("YUV AVI only supports even dimensions"); } + /** @throws IllegalArgumentException if dimensions are not even */ public Decoded2YuvAvi(@Nonnull File outputFile, int iWidth, int iHeight, @Nonnull AudioVideoSync avSync, @Nonnull AudioFormat af, @Nonnull ILocalizedLogger log) { super(outputFile, iWidth, iHeight, avSync, af, log); + if (((iWidth | iHeight) & 1) != 0) + throw new IllegalArgumentException("YUV AVI only supports even dimensions"); } public void assertAcceptsDecoded(@Nonnull MdecDecoder decoder) throws IllegalArgumentException { diff --git a/jpsxdec/src/jpsxdec/modules/video/save/VideoFormat.java b/jpsxdec/src/jpsxdec/modules/video/save/VideoFormat.java index 8a89db0..afea025 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/VideoFormat.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/VideoFormat.java @@ -61,13 +61,15 @@ public enum VideoFormat { public String getExtension() { return ".avi"; } public boolean isAvi() { return true; } public int getDecodeQualityCount() { return 1; } - public MdecDecodeQuality getMdecDecodeQuality(int i) { return MdecDecodeQuality.HIGH_PLUS; } + public MdecDecodeQuality getMdecDecodeQuality(int i) { return MdecDecodeQuality.HIGH; } + public boolean mustHaveEvenDims() { return true; }; }, AVI_JYUV(I.VID_AVI_JYUV_DESCRIPTION(), I.VID_AVI_JYUV_COMMAND()) { public String getExtension() { return ".avi"; } public boolean isAvi() { return true; } public int getDecodeQualityCount() { return 1; } - public MdecDecodeQuality getMdecDecodeQuality(int i) { return MdecDecodeQuality.HIGH_PLUS; } + public MdecDecodeQuality getMdecDecodeQuality(int i) { return MdecDecodeQuality.HIGH; } + public boolean mustHaveEvenDims() { return true; }; }, IMGSEQ_PNG(I.VID_IMG_SEQ_PNG_DESCRIPTION(), I.VID_IMG_SEQ_PNG_COMMAND(), JavaImageFormat.PNG) @@ -131,6 +133,7 @@ public boolean isAvailable() { } public boolean isCroppable() { return true; } + public boolean mustHaveEvenDims() { return false; }; public int getDecodeQualityCount() { return MdecDecodeQuality.values().length; } public @Nonnull MdecDecodeQuality getMdecDecodeQuality(int i) { return MdecDecodeQuality.values()[i]; } diff --git a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaver.java b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaver.java index 90b9c60..94136c0 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaver.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaver.java @@ -54,7 +54,6 @@ import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.modules.IIdentifiedSector; import jpsxdec.modules.SectorClaimSystem; -import jpsxdec.modules.sharedaudio.DecodedAudioPacket; import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; import jpsxdec.modules.video.DiscItemVideoStream; import jpsxdec.modules.video.IDemuxedFrame; @@ -63,14 +62,15 @@ import jpsxdec.modules.video.framenumber.FrameCompareIs; import jpsxdec.modules.video.framenumber.FrameLookup; import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.ChromaUpsample; import jpsxdec.psxvideo.mdec.MdecDecoder; -import jpsxdec.psxvideo.mdec.MdecDecoder_double_interpolate; +import jpsxdec.psxvideo.mdec.MdecDecoder_double; import jpsxdec.util.IO; import jpsxdec.util.TaskCanceledException; /** Constructs a {@link VDP Video decoder pipeline} from a * {@link VideoSaverBuilder} and performs the actual saving of video. */ -public class VideoSaver implements DecodedAudioPacket.Listener { +public class VideoSaver { private static final Logger LOG = Logger.getLogger(VideoSaver.class.getName()); @@ -80,81 +80,188 @@ public class VideoSaver implements DecodedAudioPacket.Listener { private final VideoSaverBuilder _vsb; @Nonnull private final VideoFormat _videoFormat; - @Nonnull - private final VDP.GeneratedFileListener _genFileListener; @CheckForNull private final File _directory; + private final AutowireVDP _pipeline = new AutowireVDP(); + + private final int _iStartSector; + private final int _iEndSector; + @Nonnull + private final FrameToBitstreamFilter _frame2bitstream; @CheckForNull - private VDP.ToAvi _toAvi; + private final ISectorAudioDecoder _audioDecoder; public VideoSaver(@Nonnull DiscItemVideoStream vidItem, @Nonnull VideoSaverBuilder vsb, @Nonnull VDP.GeneratedFileListener genFileListener, - @CheckForNull File directory) + @CheckForNull File directory, + @Nonnull ILocalizedLogger log, + @Nonnull ISectorClaimToDemuxedFrame demuxer, + @CheckForNull ISectorAudioDecoder audioDecoder) { _vidItem = vidItem; _vsb = vsb; _videoFormat = vsb.getVideoFormat(); - _genFileListener = genFileListener; _directory = directory; + _audioDecoder = audioDecoder; + + _pipeline.setMap(demuxer); + _pipeline.setFileListener(genFileListener); + + VDP.ToAvi toAvi = null; + switch (_videoFormat) { + + case IMGSEQ_BITSTREAM: { + VDP.Bitstream2File bs2f = new VDP.Bitstream2File(makeFormatter(), log); + _pipeline.setMap(bs2f); + } + break; + + case IMGSEQ_MDEC: { + addBitstream2Mdec(); + VDP.Mdec2File m2f = new VDP.Mdec2File(makeFormatter(), _vsb.getWidth(), _vsb.getHeight(), log); + _pipeline.setMap(m2f); + } break; + + case IMGSEQ_BMP: + case IMGSEQ_PNG: { + addBitstream2Mdec(); + addMdec2Decoded(log); + JavaImageFormat javaImgFmt = _videoFormat.getImgFmt(); + VDP.Decoded2JavaImage d2j = new VDP.Decoded2JavaImage( + makeFormatter(), javaImgFmt, _vsb.getWidth(), _vsb.getHeight(), log); + _pipeline.setMap(d2j); + } break; + + case IMGSEQ_JPG: { + addBitstream2Mdec(); + VDP.Mdec2Jpeg m2jpg = new VDP.Mdec2Jpeg(makeFormatter(), _vsb.getWidth(), _vsb.getHeight(), log); + _pipeline.setMap(m2jpg); + } break; + + case AVI_MJPG: { + addBitstream2Mdec(); + VDP.Mdec2MjpegAvi m2mjpg; + if (_audioDecoder == null) + m2mjpg = new VDP.Mdec2MjpegAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); + else + m2mjpg = new VDP.Mdec2MjpegAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(_audioDecoder), _audioDecoder.getOutputFormat(), log); + _pipeline.setToAvi(m2mjpg); + toAvi = m2mjpg; + } break; + + case AVI_JYUV: { + addBitstream2Mdec(); + addMdec2Decoded(log); + VDP.Decoded2JYuvAvi d2jyuv; + if (_audioDecoder == null) + d2jyuv = new VDP.Decoded2JYuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); + else + d2jyuv = new VDP.Decoded2JYuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(_audioDecoder), _audioDecoder.getOutputFormat(), log); + _pipeline.setToAvi(d2jyuv); + toAvi = d2jyuv; + } break; + + case AVI_YUV: { + addBitstream2Mdec(); + addMdec2Decoded(log); + VDP.Decoded2YuvAvi d2yuv; + if (_audioDecoder == null) + d2yuv = new VDP.Decoded2YuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); + else + d2yuv = new VDP.Decoded2YuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(_audioDecoder), _audioDecoder.getOutputFormat(), log); + _pipeline.setToAvi(d2yuv); + toAvi = d2yuv; + } break; + + case AVI_RGB: { + addBitstream2Mdec(); + addMdec2Decoded(log); + VDP.Decoded2RgbAvi d2rgb; + if (_audioDecoder == null) + d2rgb = new VDP.Decoded2RgbAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); + else + d2rgb = new VDP.Decoded2RgbAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(_audioDecoder), _audioDecoder.getOutputFormat(), log); + _pipeline.setToAvi(d2rgb); + toAvi = d2rgb; + } break; + + default: + throw new RuntimeException(); + } + + if (_audioDecoder == null) { + _iStartSector = _vidItem.getStartSector(); + _iEndSector = _vidItem.getEndSector(); + _frame2bitstream = new FrameToBitstreamFilter(_vsb.getFileNumberType(), _vsb.getSaveStartFrame(), _vsb.getSaveEndFrame(), log); + } else { + _pipeline.setAudioDecoder(_audioDecoder); + if (toAvi != null) + _pipeline.setAudioPacketListener(toAvi); + + _iStartSector = Math.min(_vidItem.getStartSector(), + _audioDecoder.getStartSector()); + _iEndSector = Math.max(_vidItem.getEndSector(), + _audioDecoder.getEndSector()); + // when saving with audio, you can't choose start/end frames + _frame2bitstream = new FrameToBitstreamFilter(_vsb.getFileNumberType(), null, null, log); + } + _pipeline.setMap(_frame2bitstream); + } + + private void addBitstream2Mdec() { + VDP.Bitstream2Mdec bs2m = new VDP.Bitstream2Mdec(); + _pipeline.setMap(bs2m); + } + + private void addMdec2Decoded(@Nonnull ILocalizedLogger log) { + MdecDecodeQuality quality = _vsb.getDecodeQuality(); + MdecDecoder vidDecoder = quality.makeDecoder(_vidItem.getWidth(), _vidItem.getHeight()); + if (vidDecoder instanceof MdecDecoder_double) { + ChromaUpsample chroma = _vsb.getChromaInterpolation(); + ((MdecDecoder_double)vidDecoder).setUpsampler(chroma); + } + + VDP.Mdec2Decoded mdec2decode = new VDP.Mdec2Decoded(vidDecoder, log); + _pipeline.setMap(mdec2decode); } private void startup(@Nonnull ILocalizedLogger log) throws LoggedFailure { - if (_toAvi != null) { + VDP.ToAvi avi = _pipeline.getAvi(); + if (avi != null) { try { - _toAvi.open(); + avi.open(); } catch (LocalizedFileNotFoundException ex) { throw new LoggedFailure(log, Level.SEVERE, ex.getSourceMessage(), ex); } catch (FileNotFoundException ex) { - throw new LoggedFailure(log, Level.SEVERE, I.IO_OPENING_FILE_ERROR_NAME(_toAvi.getOutputFile().toString()), ex); + throw new LoggedFailure(log, Level.SEVERE, I.IO_OPENING_FILE_ERROR_NAME(avi.getOutputFile().toString()), ex); } catch (IOException ex) { - throw new LoggedFailure(log, Level.SEVERE, I.IO_WRITING_TO_FILE_ERROR_NAME(_toAvi.getOutputFile().toString()), ex); + throw new LoggedFailure(log, Level.SEVERE, I.IO_WRITING_TO_FILE_ERROR_NAME(avi.getOutputFile().toString()), ex); } } } private void shutdown() { - if (_toAvi != null) - IO.closeSilently(_toAvi, LOG); + VDP.ToAvi avi = _pipeline.getAvi(); + if (avi != null) + IO.closeSilently(avi, LOG); } - public void save(@Nonnull ProgressLogger pl, - @Nonnull ISectorClaimToDemuxedFrame demuxer, - @CheckForNull ISectorAudioDecoder audioDecoder) - throws LoggedFailure, TaskCanceledException - { - VDP.IBitstreamListener bsListener = setupDecode(pl, audioDecoder); - - final int iStartSector, iEndSector; - FrameToBitstream f2bs; - if (audioDecoder == null) { - iStartSector = _vidItem.getStartSector(); - iEndSector = _vidItem.getEndSector(); - f2bs = new FrameToBitstream(bsListener, _vsb.getFileNumberType(), _vsb.getSaveStartFrame(), _vsb.getSaveEndFrame(), pl); - } else { - iStartSector = Math.min(_vidItem.getStartSector(), - audioDecoder.getStartSector()); - iEndSector = Math.max(_vidItem.getEndSector(), - audioDecoder.getEndSector()); - // when saving with audio, you can't choose start/end frames - f2bs = new FrameToBitstream(bsListener, _vsb.getFileNumberType(), null, null, pl); - } + public void save(@Nonnull ProgressLogger pl) throws LoggedFailure, TaskCanceledException { + SectorClaimSystem it = SectorClaimSystem.create(_vidItem.getSourceCd(), _iStartSector, _iEndSector); + _pipeline.attachToSectorClaimer(it); - demuxer.setFrameListener(f2bs); + // finish setting up the pipeline + _pipeline.autowire(); + pl.progressStart(_iEndSector - _iStartSector + 1); startup(pl); - try { - pl.progressStart(iEndSector - iStartSector + 1); - SectorClaimSystem it = SectorClaimSystem.create(_vidItem.getSourceCd(), iStartSector, iEndSector); - demuxer.attachToSectorClaimer(it); - if (audioDecoder != null) - audioDecoder.attachToSectorClaimer(it); for (int iSector = 0; it.hasNext(); iSector++) { IIdentifiedSector identifiedSector; // keep it out here so I can see what it was while debugging try { @@ -164,16 +271,16 @@ public void save(@Nonnull ProgressLogger pl, I.IO_READING_FROM_FILE_ERROR_NAME(ex.getFile().toString()), ex); } - sendEvent(pl, f2bs); + sendLogEvent(pl, _frame2bitstream); pl.progressUpdate(iSector); // if we've already handled the frames we want to save, break early - if (f2bs.isDone()) + if (_frame2bitstream.isDone()) break; } it.close(pl); - sendEvent(pl, f2bs); + sendLogEvent(pl, _frame2bitstream); pl.progressEnd(); } finally { shutdown(); @@ -181,8 +288,8 @@ public void save(@Nonnull ProgressLogger pl, } - private @Nonnull void sendEvent(@Nonnull ProgressLogger pl, - @Nonnull FrameToBitstream f2bs) + private @Nonnull void sendLogEvent(@Nonnull ProgressLogger pl, + @Nonnull FrameToBitstreamFilter f2bs) { if (!pl.isSeekingEvent()) return; @@ -205,126 +312,6 @@ public void save(@Nonnull ProgressLogger pl, } - private @Nonnull VDP.IBitstreamListener setupDecode(@Nonnull ILocalizedLogger log, @CheckForNull ISectorAudioDecoder audio) { - final VDP.IBitstreamListener bsListener; - if (_videoFormat == VideoFormat.IMGSEQ_BITSTREAM) { - VDP.Bitstream2File bs2f = new VDP.Bitstream2File(makeFormatter(), log); - bs2f.setGenFileListener(_genFileListener); - bsListener = bs2f; - } else { - bsListener = mdec(log, audio); - } - - return bsListener; - } - - private @Nonnull VDP.IBitstreamListener mdec(@Nonnull ILocalizedLogger log, @CheckForNull ISectorAudioDecoder audio) { - final VDP.IMdecListener mdecListener; - switch (_videoFormat) { - case IMGSEQ_MDEC: { - VideoFileNameFormatter outFileFormat = makeFormatter(); - VDP.Mdec2File m2f = new VDP.Mdec2File(outFileFormat, _vsb.getWidth(), _vsb.getHeight(), log); - m2f.setGenFileListener(_genFileListener); - mdecListener = m2f; - } break; - case IMGSEQ_JPG: { - VDP.Mdec2Jpeg m2jpg = new VDP.Mdec2Jpeg(makeFormatter(), _vsb.getWidth(), _vsb.getHeight(), log); - m2jpg.setGenFileListener(_genFileListener); - mdecListener = m2jpg; - } break; - case AVI_MJPG: { - if (audio == null) { - VDP.Mdec2MjpegAvi x = new VDP.Mdec2MjpegAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); - _toAvi = x; - mdecListener = x; - } else { - VDP.Mdec2MjpegAvi x = new VDP.Mdec2MjpegAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(audio), audio.getOutputFormat(), log); - _toAvi = x; - mdecListener = x; - audio.setAudioListener(this); - } - _toAvi.setGenFileListener(_genFileListener); - } break; - default: - mdecListener = toDecoded(log, audio); - } - return new VDP.Bitstream2Mdec(mdecListener); - } - - - - private @Nonnull VDP.IMdecListener toDecoded(@Nonnull ILocalizedLogger log, @CheckForNull ISectorAudioDecoder audio) { - final VDP.IDecodedListener decodedListener; - if (_videoFormat == VideoFormat.IMGSEQ_BMP || _videoFormat == VideoFormat.IMGSEQ_PNG) { - JavaImageFormat javaImgFmt = _videoFormat.getImgFmt(); - VDP.Decoded2JavaImage d2j = new VDP.Decoded2JavaImage( - makeFormatter(), javaImgFmt, _vsb.getWidth(), _vsb.getHeight(), log); - d2j.setGenFileListener(_genFileListener); - decodedListener = d2j; - } else { - decodedListener = toAvi(log, audio); - } - - MdecDecodeQuality quality = _vsb.getDecodeQuality(); - MdecDecoder vidDecoder = quality.makeDecoder(_vidItem.getWidth(), _vidItem.getHeight()); - if (vidDecoder instanceof MdecDecoder_double_interpolate) { - MdecDecoder_double_interpolate.Upsampler chroma = _vsb.getChromaInterpolation(); - ((MdecDecoder_double_interpolate)vidDecoder).setResampler(chroma); - } - - VDP.Mdec2Decoded mdec2decode = new VDP.Mdec2Decoded(vidDecoder, log); - mdec2decode.setDecoded(decodedListener); - return mdec2decode; - } - - private VDP.IDecodedListener toAvi(@Nonnull ILocalizedLogger log, @CheckForNull ISectorAudioDecoder audio) { - final VDP.IDecodedListener toDec; - if (audio == null) { - switch (_videoFormat) { - case AVI_JYUV: { - VDP.Decoded2JYuvAvi d2 = new VDP.Decoded2JYuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); - _toAvi = d2; - toDec = d2; - } break; - case AVI_YUV: { - VDP.Decoded2YuvAvi d2 = new VDP.Decoded2YuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); - _toAvi = d2; - toDec = d2; - } break; - case AVI_RGB: { - VDP.Decoded2RgbAvi d2 = new VDP.Decoded2RgbAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeVSync(), log); - _toAvi = d2; - toDec = d2; - } break; - default: - throw new RuntimeException(); - } - } else { - switch (_videoFormat) { - case AVI_JYUV: { - VDP.Decoded2JYuvAvi d2 = new VDP.Decoded2JYuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(audio), audio.getOutputFormat(), log); - _toAvi = d2; - toDec = d2; - } break; - case AVI_YUV: { - VDP.Decoded2YuvAvi d2 = new VDP.Decoded2YuvAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(audio), audio.getOutputFormat(), log); - _toAvi = d2; - toDec = d2; - } break; - case AVI_RGB: { - VDP.Decoded2RgbAvi d2 = new VDP.Decoded2RgbAvi(getAviFile(), _vsb.getWidth(), _vsb.getHeight(), makeAvSync(audio), audio.getOutputFormat(), log); - _toAvi = d2; - toDec = d2; - } break; - default: - throw new RuntimeException(); - } - audio.setAudioListener(this); - } - _toAvi.setGenFileListener(_genFileListener); - return toDec; - } - private @Nonnull VideoSync makeVSync() { VideoSync vidSync = new VideoSync(_vidItem.getAbsolutePresentationStartSector(), getSectorsPerSecond(), @@ -358,25 +345,7 @@ private int getSectorsPerSecond() { // ============== - /** Captures any output {@link LoggedFailure} and unwinds the stack so the - * {@link LoggedFailure} can be thrown outside the pipeline. */ - private static class UnwindException extends RuntimeException { - @Nonnull - private final LoggedFailure _fail; - public UnwindException(@Nonnull LoggedFailure fail) { - _fail = fail; - } - public LoggedFailure getFailure() { - return _fail; - } - } - - private static class FrameToBitstream implements IDemuxedFrame.Listener { - @Nonnull - private final VDP.IBitstreamListener _bsListener; - - @Nonnull - private final FrameNumber.Type _frameNumberType; + private static class FrameToBitstreamFilter extends Frame2Bitstream { @CheckForNull private final FrameLookup _startFrame; @@ -390,19 +359,19 @@ private static class FrameToBitstream implements IDemuxedFrame.Listener { private FrameNumber _currentFrame = null; private boolean _blnIsDone = false; - public FrameToBitstream(@Nonnull VDP.IBitstreamListener bsListener, - @Nonnull FrameNumber.Type frameNumberType, - @CheckForNull FrameLookup startFrame, - @CheckForNull FrameLookup endFrame, - @Nonnull ILocalizedLogger log) + public FrameToBitstreamFilter(@Nonnull FrameNumber.Type frameNumberType, + @CheckForNull FrameLookup startFrame, + @CheckForNull FrameLookup endFrame, + @Nonnull ILocalizedLogger log) { - _bsListener = bsListener; - _frameNumberType = frameNumberType; + super(frameNumberType); _startFrame = startFrame; _endFrame = endFrame; _log = log; } - public void frameComplete(@Nonnull IDemuxedFrame frame) { + + @Override + public void frameComplete(@Nonnull IDemuxedFrame frame) throws LoggedFailure { _currentFrame = frame.getFrame(); if ((_startFrame != null && _startFrame.compareTo(_currentFrame) == FrameCompareIs.GREATERTHAN)) return; // haven't received a starting frame yet @@ -410,26 +379,22 @@ public void frameComplete(@Nonnull IDemuxedFrame frame) { _blnIsDone = true; return; // have past the end frame } - byte[] abBitstream = frame.copyDemuxData(); - try { - FormattedFrameNumber ffn = frame.getFrame().getNumber(_frameNumberType); - if (ffn == null) { - // if this video saver was created correctly, the frame number - // type (i.e. header) should be available in every frame number - // if not, something weird happened - - // this could get ugly down the pipeline if - // individual frame files are expected. - // without a frame number, it will keep - // generating the same file name over and over - _log.log(Level.SEVERE, I.FRAME_MISSING_FRAME_NUMBER_HEADER(frame.getFrame().getIndexNumber().getFrameValue())); - // maybe it would be better to just throw here? - } - _bsListener.bitstream(abBitstream, frame.getDemuxSize(), ffn, frame.getPresentationSector()); - } catch (LoggedFailure ex) { - // fire the exception outside of the pipeline - throw new UnwindException(ex); + + FormattedFrameNumber ffn = frame.getFrame().getNumber(getFrameNumberType()); + if (ffn == null) { + // if this video saver was created correctly, the frame number + // type (i.e. header) should be available in every frame number + // if not, something weird happened + + // this could get ugly down the pipeline if + // individual frame files are expected. + // without a frame number, it will keep + // generating the same file name over and over + _log.log(Level.SEVERE, I.FRAME_MISSING_FRAME_NUMBER_HEADER(frame.getFrame().getIndexNumber().getFrameValue())); + // maybe it would be better to just throw here? } + + super.frameComplete(frame); } public @CheckForNull FrameNumber getCurrentFrame() { return _currentFrame; @@ -439,14 +404,4 @@ public boolean isDone() { } } - public void audioPacketComplete(DecodedAudioPacket packet, - ILocalizedLogger log) - { - try { - _toAvi.writeAudio(packet); - } catch (LoggedFailure ex) { - // intercept the exception, then unwind outside of the pipeline - throw new UnwindException(ex); - } - } } diff --git a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverBuilder.java b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverBuilder.java index 131a56e..be2b5a7 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverBuilder.java @@ -40,7 +40,9 @@ import argparser.BooleanHolder; import argparser.StringHolder; import java.io.File; +import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -53,13 +55,14 @@ import jpsxdec.i18n.UnlocalizedMessage; import jpsxdec.i18n.exception.LocalizedDeserializationFail; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.modules.video.DiscItemVideoStream; import jpsxdec.modules.video.framenumber.FormattedFrameNumber; import jpsxdec.modules.video.framenumber.FrameLookup; import jpsxdec.modules.video.framenumber.FrameNumber; import jpsxdec.psxvideo.mdec.Calc; -import jpsxdec.psxvideo.mdec.MdecDecoder_double_interpolate.Upsampler; +import jpsxdec.psxvideo.mdec.ChromaUpsample; import jpsxdec.util.ArgParser; import jpsxdec.util.Fraction; import jpsxdec.util.TaskCanceledException; @@ -72,9 +75,15 @@ public abstract class VideoSaverBuilder extends DiscItemSaverBuilder { @Nonnull private final DiscItemVideoStream _sourceVidItem; + private final List _imgFmtList = VideoFormat.getAvailable(); + private final List _imgFmtListNoBs; + + protected VideoSaverBuilder(@Nonnull DiscItemVideoStream vidItem) { _sourceVidItem = vidItem; _types = vidItem.getFrameNumberTypes(); + _imgFmtListNoBs = new ArrayList(_imgFmtList); + _imgFmtListNoBs.remove(VideoFormat.IMGSEQ_BITSTREAM); } public boolean copySettingsTo(@Nonnull DiscItemSaverBuilder otherBuilder) { @@ -170,12 +179,17 @@ public boolean getSingleSpeed_enabled() { // ......................................................................... - private final List _imgFmtList = VideoFormat.getAvailable(); public @Nonnull VideoFormat getVideoFormat_listItem(int i) { - return _imgFmtList.get(i); + if (_sourceVidItem.hasIndependentBitstream()) + return _imgFmtList.get(i); + else + return _imgFmtListNoBs.get(i); } public int getVideoFormat_listSize() { - return _imgFmtList.size(); + if (_sourceVidItem.hasIndependentBitstream()) + return _imgFmtList.size(); + else + return _imgFmtListNoBs.size(); } @Nonnull @@ -184,6 +198,13 @@ public int getVideoFormat_listSize() { return _videoFormat; } public void setVideoFormat(@Nonnull VideoFormat val) { + if (_sourceVidItem.hasIndependentBitstream()) { + if (!_imgFmtList.contains(val)) + throw new IllegalArgumentException(); + } else { + if (!_imgFmtListNoBs.contains(val)) + throw new IllegalArgumentException(); + } _videoFormat = val; firePossibleChange(); } @@ -206,17 +227,32 @@ public boolean getCrop_enabled() { } public int getWidth() { - if (getCrop()) - return _sourceVidItem.getWidth(); - else + if (getCrop()) { + if (getVideoFormat().mustHaveEvenDims() && _sourceVidItem.getWidth() % 2 == 1) { + return _sourceVidItem.getWidth() + 1; + } else { + return _sourceVidItem.getWidth(); + } + } else { return Calc.fullDimension(_sourceVidItem.getWidth()); + } } public int getHeight() { - if (getCrop()) - return _sourceVidItem.getHeight(); - else + if (getCrop()) { + if (getVideoFormat().mustHaveEvenDims() && _sourceVidItem.getHeight() % 2 == 1) { + return _sourceVidItem.getHeight() + 1; + } else { + return _sourceVidItem.getHeight(); + } + } else { return Calc.fullDimension(_sourceVidItem.getHeight()); + } + } + + public boolean makingDimensionsEven() { + return getCrop() && getVideoFormat().mustHaveEvenDims() && + ((_sourceVidItem.getWidth() | _sourceVidItem.getHeight() & 1) != 0); } // ......................................................................... @@ -229,7 +265,7 @@ public int getDecodeQuality_listSize() { } @CheckForNull - private MdecDecodeQuality _decodeQuality = MdecDecodeQuality.HIGH_PLUS; + private MdecDecodeQuality _decodeQuality = MdecDecodeQuality.HIGH; public @CheckForNull MdecDecodeQuality getDecodeQuality() { if (getVideoFormat().getDecodeQualityCount() == 1) return getVideoFormat().getMdecDecodeQuality(0); @@ -247,29 +283,29 @@ public boolean getDecodeQuality_enabled() { // ......................................................................... @Nonnull - private Upsampler _mdecUpsampler = Upsampler.Bicubic; + private ChromaUpsample _mdecUpsampler = ChromaUpsample.Bicubic; public boolean getChromaInterpolation_enabled() { MdecDecodeQuality q = getDecodeQuality(); return getDecodeQuality_enabled() && q != null && q.canUpsample(); } - public @Nonnull Upsampler getChromaInterpolation_listItem(int i) { - return Upsampler.values()[i]; + public @Nonnull ChromaUpsample getChromaInterpolation_listItem(int i) { + return ChromaUpsample.values()[i]; } public int getChromaInterpolation_listSize() { - return Upsampler.values().length; + return ChromaUpsample.values().length; } - public @Nonnull Upsampler getChromaInterpolation() { + public @Nonnull ChromaUpsample getChromaInterpolation() { if (getChromaInterpolation_enabled()) return _mdecUpsampler; else - return Upsampler.NearestNeighbor; + return ChromaUpsample.NearestNeighbor; } - public void setChromaInterpolation(@Nonnull Upsampler val) { + public void setChromaInterpolation(@Nonnull ChromaUpsample val) { _mdecUpsampler = val; firePossibleChange(); } @@ -351,7 +387,7 @@ protected void makeHelpTable(@Nonnull TabularFeedback tfb) { tfb.newRow(); tfb.addCell(I.CMD_VIDEO_QUALITY()); - c = new Cell(I.CMD_VIDEO_QUALITY_HELP(MdecDecodeQuality.HIGH_PLUS.getCmdLine())); + c = new Cell(I.CMD_VIDEO_QUALITY_HELP(MdecDecodeQuality.HIGH.getCmdLine())); for (MdecDecodeQuality quality : MdecDecodeQuality.values()) { c.addLine(quality.getCmdLine(), 2); } @@ -360,8 +396,8 @@ protected void makeHelpTable(@Nonnull TabularFeedback tfb) { tfb.newRow(); tfb.addCell(I.CMD_VIDEO_UP()); - c = new Cell(I.CMD_VIDEO_UP_HELP(Upsampler.Bicubic.getDescription())); - for (Upsampler up : Upsampler.values()) { + c = new Cell(I.CMD_VIDEO_UP_HELP(ChromaUpsample.Bicubic.getDescription())); + for (ChromaUpsample up : ChromaUpsample.values()) { c.addLine(up.getCmdLineHelp(), 2); } tfb.addCell(c); @@ -455,7 +491,7 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb throw new RuntimeException("Only header type should ever be unsupported here"); } } else { - fbs.printlnWarn(I.CMD_FRAME_NUMBER_TYPE_INVALID(num.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(num.value, "-num")); } } @@ -468,7 +504,7 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb if (vf != null) setVideoFormat(vf); else - fbs.printlnWarn(I.CMD_VIDEO_FORMAT_INVALID(vidfmt.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(vidfmt.value, "-vf,-vidfmt")); } if (quality.value != null) { @@ -476,15 +512,15 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb if (dq != null) setDecodeQuality(dq); else - fbs.printlnWarn(I.CMD_DECODE_QUALITY_INVALID(quality.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(quality.value, "-q,-quality")); } if (up.value != null) { - Upsampler upsampler = Upsampler.fromCmdLine(up.value); + ChromaUpsample upsampler = ChromaUpsample.fromCmdLine(up.value); if (upsampler != null) setChromaInterpolation(upsampler); else - fbs.printlnWarn(I.CMD_UPSAMPLE_QUALITY_INVALID(up.value)); + fbs.printlnWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(up.value, "-up")); } setCrop(!nocrop.value); @@ -495,60 +531,64 @@ public void commandLineOptions(@Nonnull ArgParser ap, @Nonnull FeedbackStream fb } else if ("2".equals(discSpeed.value)) { setSingleSpeed(false); } else { - fbs.printWarn(I.CMD_IGNORING_INVALID_DISC_SPEED(discSpeed.value)); + fbs.printWarn(I.CMD_IGNORING_INVALID_VALUE_FOR_CMD(discSpeed.value, "-ds")); } } } @Override - public void printSelectedOptions(@Nonnull FeedbackStream fbs) { + public void printSelectedOptions(@Nonnull ILocalizedLogger log) { VideoFormat vidFmt = getVideoFormat(); + log.log(Level.INFO, I.CMD_VIDEO_FORMAT(getVideoFormat().toString())); + if (vidFmt.getDecodeQualityCount() > 0) { MdecDecodeQuality quality = getDecodeQuality(); - fbs.println(I.CMD_DECODE_QUALITY(quality.toString())); + log.log(Level.INFO, I.CMD_DECODE_QUALITY(quality.toString())); if (quality.canUpsample()) { - Upsampler chroma = getChromaInterpolation(); - fbs.println(I.CMD_UPSAMPLE_QUALITY(chroma.toString())); + ChromaUpsample chroma = getChromaInterpolation(); + log.log(Level.INFO, I.CMD_UPSAMPLE_QUALITY(chroma.getDescription().getLocalizedMessage())); } } if (getCrop_enabled()) - fbs.println(I.CMD_CROPPING(getCrop() ? 1 : 0)); + log.log(Level.INFO, I.CMD_CROPPING(getCrop() ? 1 : 0)); - fbs.println(I.CMD_VIDEO_FORMAT(getVideoFormat().toString())); + if (makingDimensionsEven()) + log.log(Level.INFO, I.CMD_VIDEO_MUST_HAVE_EVEN_DIMS(getVideoFormat().toString())); + log.log(Level.INFO, I.CMD_DIMENSIONS(getWidth(), getHeight())); if (vidFmt.isSequence()) { File[] _aoOutRng = getOutputFileRange(); if (_aoOutRng.length == 1) { - fbs.println(I.CMD_OUTPUT_FILE(_aoOutRng[0])); + log.log(Level.INFO, I.CMD_OUTPUT_FILE(_aoOutRng[0])); } else { - fbs.println(I.CMD_OUTPUT_FILES(_aoOutRng[0], _aoOutRng[1])); + log.log(Level.INFO, I.CMD_OUTPUT_FILES(_aoOutRng[0], _aoOutRng[1])); } } else { File outFile = VideoFileNameFormatter.singleFile(null, _sourceVidItem, vidFmt); - fbs.println(I.CMD_DISC_SPEED(getSingleSpeed() ? 1 : 2, getFps().asDouble())); + log.log(Level.INFO, I.CMD_DISC_SPEED(getSingleSpeed() ? 1 : 2, getFps().asDouble())); if (getSavingAudio()) { - fbs.println(I.CMD_SAVING_WITH_AUDIO_ITEMS()); - printSelectedAudioOptions(fbs); - fbs.println(I.CMD_EMULATE_PSX_AV_SYNC_NY(getEmulatePsxAvSync() ? 1 : 0)); + log.log(Level.INFO, I.CMD_SAVING_WITH_AUDIO_ITEMS()); + printSelectedAudioOptions(log); + log.log(Level.INFO, I.CMD_EMULATE_PSX_AV_SYNC_NY(getEmulatePsxAvSync() ? 1 : 0)); } else { - fbs.println(I.CMD_NO_AUDIO()); + log.log(Level.INFO, I.CMD_NO_AUDIO()); } - fbs.println(I.CMD_SAVING_AS(outFile)); + log.log(Level.INFO, I.CMD_SAVING_AS(outFile)); } FrameLookup startFrame = getSaveStartFrame(); FrameLookup endFrame = getSaveEndFrame(); if (startFrame != null) - fbs.println(I.CMD_FRAME_RANGE_BEFORE(startFrame.toString())); + log.log(Level.INFO, I.CMD_FRAME_RANGE_BEFORE(startFrame.toString())); if (endFrame != null) - fbs.println(I.CMD_FRAME_RANGE_AFTER(endFrame.toString())); + log.log(Level.INFO, I.CMD_FRAME_RANGE_AFTER(endFrame.toString())); } - abstract protected void printSelectedAudioOptions(@Nonnull FeedbackStream fbs); + abstract protected void printSelectedAudioOptions(@Nonnull ILocalizedLogger log); @Override diff --git a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverPanel.java b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverPanel.java index 14a1a7e..4501bc3 100644 --- a/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverPanel.java +++ b/jpsxdec/src/jpsxdec/modules/video/save/VideoSaverPanel.java @@ -54,7 +54,7 @@ import jpsxdec.discitems.CombinedBuilderListener; import jpsxdec.discitems.ParagraphPanel; import jpsxdec.i18n.I; -import jpsxdec.psxvideo.mdec.MdecDecoder_double_interpolate.Upsampler; +import jpsxdec.psxvideo.mdec.ChromaUpsample; import jpsxdec.util.Fraction; /** Abstract {@link ParagraphPanel} shared among video @@ -123,7 +123,7 @@ public Object getElementAt(int index) { return _bl.getBuilder().getChromaInterpolation_listItem(index); } public void setSelectedItem(Object anItem) { - _bl.getBuilder().setChromaInterpolation((Upsampler) anItem); + _bl.getBuilder().setChromaInterpolation((ChromaUpsample) anItem); } public Object getSelectedItem() { return _bl.getBuilder().getChromaInterpolation(); diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/DemuxedFrameWithNumberAndDims.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/DemuxedFrameWithNumberAndDims.java index 9d07c60..2450092 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/DemuxedFrameWithNumberAndDims.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/DemuxedFrameWithNumberAndDims.java @@ -46,6 +46,7 @@ import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.video.IDemuxedFrame; import jpsxdec.modules.video.framenumber.FrameNumber; +import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.DemuxedData; import jpsxdec.util.Fraction; @@ -53,7 +54,8 @@ public class DemuxedFrameWithNumberAndDims implements IDemuxedFrame { public interface Listener { - void frameComplete(@Nonnull DemuxedFrameWithNumberAndDims frame, @Nonnull ILocalizedLogger log); + void frameComplete(@Nonnull DemuxedFrameWithNumberAndDims frame, @Nonnull ILocalizedLogger log) + throws LoggedFailure; void endOfSectors(@Nonnull ILocalizedLogger log); } @@ -83,6 +85,10 @@ public void setFrame(@Nonnull FrameNumber frameNumber) { return _frameNumber; } + public @CheckForNull MdecInputStream getCustomFrameMdecStream() { + return null; + } + public int getWidth() { return _iWidth; } public int getHeight() { return _iHeight; } public int getStartSector() { return _demux.getStartSector(); } @@ -113,7 +119,7 @@ public void writeToSectors(@Nonnull byte[] abNewDemux, @Override public String toString() { - return String.format("Frame %d sectors %d-%d %dx%d %d chunks %d bytes", + return String.format("Frame %d sectors %d-%d %dx%d chunks:%d bytes:%d", getHeaderFrameNumber(), getStartSector(), getEndSector(), getWidth(), getHeight(), _demux.getPieceCount(), diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/DiscItemSectorBasedVideoStream.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/DiscItemSectorBasedVideoStream.java index 3c46fab..0ac3a45 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/DiscItemSectorBasedVideoStream.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/DiscItemSectorBasedVideoStream.java @@ -65,6 +65,7 @@ import jpsxdec.modules.video.framenumber.IndexSectorFrameNumber; import jpsxdec.modules.xa.DiscItemXaAudioStream; import jpsxdec.util.Fraction; +import jpsxdec.util.Misc; import jpsxdec.util.player.PlayController; /** Represents generic sector-based PlayStation video streams @@ -169,20 +170,20 @@ final public double getApproxDuration() { } @Override - public @Nonnull ILocalizedMessage getInterestingDescription() { + final public @Nonnull ILocalizedMessage getInterestingDescription() { int iDiscSpeed = getDiscSpeed(); int iFrameCount = getFrameCount(); if (iDiscSpeed > 0) { int iSectorsPerSecond = iDiscSpeed * 75; - Date secs = new Date(0, 0, 0, 0, 0, Math.max(getSectorLength() / iSectorsPerSecond, 1)); + Date secs = Misc.dateFromSeconds(Math.max(getSectorLength() / iSectorsPerSecond, 1)); return I.GUI_STR_VIDEO_DETAILS( getWidth(), getHeight(), iFrameCount, Fraction.divide(iSectorsPerSecond, getSectorsPerFrame()).asDouble(), secs); } else { - Date secs150 = new Date(0, 0, 0, 0, 0, Math.max(getSectorLength() / 150, 1)); - Date secs75 = new Date(0, 0, 0, 0, 0, Math.max(getSectorLength() / 75, 1)); + Date secs150 = Misc.dateFromSeconds(Math.max(getSectorLength() / 150, 1)); + Date secs75 = Misc.dateFromSeconds(Math.max(getSectorLength() / 75, 1)); return I.GUI_STR_VIDEO_DETAILS_UNKNOWN_FPS( getWidth(), getHeight(), iFrameCount, @@ -229,6 +230,7 @@ public void frameComplete(IDemuxedFrame frame) { @Override public @Nonnull PlayController makePlayController() { + MediaPlayer mp; if (hasAudio()) { List audios = _parallelAudio.getLongestNonIntersectingAudioStreams(); @@ -243,10 +245,11 @@ public void frameComplete(IDemuxedFrame frame) { int iStartSector = Math.min(decoder.getStartSector(), getStartSector()); int iEndSector = Math.max(decoder.getEndSector(), getEndSector()); - return new PlayController(new MediaPlayer(this, makeDemuxer(), decoder, iStartSector, iEndSector)); + mp = new MediaPlayer(this, makeDemuxer(), decoder, iStartSector, iEndSector); } else { - return new PlayController(new MediaPlayer(this, makeDemuxer())); + mp = new MediaPlayer(this, makeDemuxer()); } + return mp.getPlayController(); } } diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/ISelfDemuxingVideoSector.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/ISelfDemuxingVideoSector.java index 71ad179..8017191 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/ISelfDemuxingVideoSector.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/ISelfDemuxingVideoSector.java @@ -37,6 +37,7 @@ package jpsxdec.modules.video.sectorbased; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.IIdentifiedSector; @@ -48,6 +49,6 @@ public interface ISelfDemuxingVideoSector extends IIdentifiedSector { public interface IDemuxer { boolean addSectorIfPartOfFrame(@Nonnull ISelfDemuxingVideoSector sector); boolean isFrameComplete(); - @Nonnull DemuxedFrameWithNumberAndDims finishFrame(@Nonnull ILocalizedLogger log); + @CheckForNull DemuxedFrameWithNumberAndDims finishFrame(@Nonnull ILocalizedLogger log); } } diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedFrameBuilder.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedFrameBuilder.java index 4ccb886..4842889 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedFrameBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedFrameBuilder.java @@ -56,6 +56,7 @@ public class SectorBasedFrameBuilder { private final int _iHeaderFrameNumber; /** Start building the frame with the first sector. */ + @SuppressWarnings("unchecked") public SectorBasedFrameBuilder(@Nonnull T firstChunk, int iChunkNumber, int iExpectedChunks, int iSector, int iHeaderFrameNumber, @@ -67,7 +68,7 @@ public SectorBasedFrameBuilder(@Nonnull T firstChunk, // if this happens, the incoming data is pretty messed up // this logic will effictively immediately end the frame log.log(Level.WARNING, - I.DEMUX_CHUNK_NUM_GTE_CHUNKS_IN_FRAME(iSector, iChunkNumber, iExpectedChunks)); + I.FRAME_NUM_CORRUPTED(String.valueOf(iHeaderFrameNumber))); _aoChunks = (T[]) new Object[iChunkNumber + 1]; } _aoChunks[iChunkNumber] = firstChunk; diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedVideoSaverBuilder.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedVideoSaverBuilder.java index 6d9725c..628f00d 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedVideoSaverBuilder.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/SectorBasedVideoSaverBuilder.java @@ -41,6 +41,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.discitems.DiscItemSaverBuilder; @@ -49,6 +50,7 @@ import jpsxdec.i18n.I; import jpsxdec.i18n.TabularFeedback; import jpsxdec.i18n.exception.LoggedFailure; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.modules.sharedaudio.DiscItemAudioStream; import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; @@ -240,9 +242,9 @@ protected void makeHelpTable(@Nonnull TabularFeedback tfb) { } @Override - protected void printSelectedAudioOptions(FeedbackStream fbs) { + protected void printSelectedAudioOptions(@Nonnull ILocalizedLogger log) { for (DiscItemAudioStream discItemAudioStream : collectSelectedAudio()) { - fbs.println(discItemAudioStream.getDetails()); + log.log(Level.INFO, discItemAudioStream.getDetails()); } } @@ -261,6 +263,7 @@ public void startSave(@Nonnull ProgressLogger pl, @CheckForNull File directory) throws LoggedFailure, TaskCanceledException { clearGeneratedFiles(); + printSelectedOptions(pl); final ISectorAudioDecoder audDecoder; final ISectorClaimToDemuxedFrame demuxer = _sourceVidItem.makeDemuxer(); @@ -272,8 +275,8 @@ else if (parallelAudio.size() == 1) else audDecoder = new AudioStreamsCombiner(parallelAudio, getAudioVolume()); - VideoSaver vs = new VideoSaver(_sourceVidItem, this, thisGeneratedFileListener, directory); - vs.save(pl, demuxer, audDecoder); + VideoSaver vs = new VideoSaver(_sourceVidItem, this, thisGeneratedFileListener, directory, pl, demuxer, audDecoder); + vs.save(pl); } } diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorCommon16byteHeader.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorCommon16byteHeader.java index 58e135a..aa45080 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorCommon16byteHeader.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorCommon16byteHeader.java @@ -58,20 +58,20 @@ public VideoSectorCommon16byteHeader(@Nonnull CdSector cdSector) { iUsedDemuxedSize = cdSector.readSInt32LE(12); } - /** @return If the chunk number is invalid according to common convention. */ - public boolean isChunkNumberStandard() { + /** @return If the chunk number is valid according to common convention. */ + public boolean hasStandardChunkNumber() { return iChunkNumber >= 0; } - /** @return If the chunk count is invalid according to common convention. */ - public boolean isChunksInFrameStandard() { + /** @return If the chunk count is valid according to common convention. */ + public boolean hasStandardChunksInFrame() { return iChunksInThisFrame >= 1; } - /** @return If the frame number is invalid according to common convention. */ - public boolean isFrameNumberStandard() { + /** @return If the frame number is valid according to common convention. */ + public boolean hasStandardFrameNumber() { return iFrameNumber >= 0; } - /** @return If the used demux size is invalid according to common convention. */ - public boolean isUsedDemuxSizeStandard() { + /** @return If the used demux size is valid according to common convention. */ + public boolean hasStandardUsedDemuxSize() { return iUsedDemuxedSize >= 1; } } diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorIdentifier.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorIdentifier.java index c96c51b..9ccc9be 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorIdentifier.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorIdentifier.java @@ -42,6 +42,7 @@ import jpsxdec.cdreaders.CdSector; import jpsxdec.modules.IIdentifiedSector; import jpsxdec.modules.SectorClaimSystem; +import jpsxdec.modules.aconcagua.SectorAconcaguaVideo; import jpsxdec.modules.granturismo.SectorGTVideo; import jpsxdec.modules.square.SectorChronoXVideo; import jpsxdec.modules.square.SectorChronoXVideoNull; @@ -52,6 +53,7 @@ import jpsxdec.modules.strvideo.SectorFF7Video; import jpsxdec.modules.strvideo.SectorIkiVideo; import jpsxdec.modules.strvideo.SectorLainVideo; +import jpsxdec.modules.strvideo.SectorReBoot; import jpsxdec.modules.strvideo.SectorStrVideo; /** Shared place for all modules to register order sensitive @@ -74,6 +76,7 @@ public class VideoSectorIdentifier { if ((vid = isVideo(new SectorIkiVideo(cdSector), cs)) != null) return vid; if ((vid = isVideo(new SectorGTVideo(cdSector), cs)) != null) return vid; if ((vid = isVideo(new SectorChronoXVideo(cdSector), cs)) != null) return vid; + if ((vid = isVideo(new SectorAconcaguaVideo(cdSector), cs)) != null) return vid; if (isMatch(new SectorChronoXVideoNull(cdSector), cs)) return null; if ((vid = isVideo(new SectorLainVideo(cdSector), cs)) != null) return vid; @@ -85,6 +88,8 @@ public class VideoSectorIdentifier { return null; } + if ((vid = isVideo(new SectorReBoot(cdSector), cs)) != null) return vid; + // FF7 has such a vague header, it can easily be falsely identified // when it should be one of the headers above if ((vid = isVideo(new SectorFF7Video(cdSector), cs)) != null) return vid; diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorWithFrameNumberDemuxer.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorWithFrameNumberDemuxer.java index 599c54c..879c9eb 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorWithFrameNumberDemuxer.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/VideoSectorWithFrameNumberDemuxer.java @@ -54,7 +54,7 @@ public class VideoSectorWithFrameNumberDemuxer implements ISelfDemuxingVideoSect private final int _iWidth, _iHeight; public VideoSectorWithFrameNumberDemuxer(@Nonnull IVideoSectorWithFrameNumber firstChunk, - @Nonnull ILocalizedLogger log) + @Nonnull ILocalizedLogger log) { _bldr = new SectorBasedFrameBuilder(firstChunk, firstChunk.getChunkNumber(), firstChunk.getChunksInFrame(), @@ -64,6 +64,18 @@ public VideoSectorWithFrameNumberDemuxer(@Nonnull IVideoSectorWithFrameNumber fi _iHeight = firstChunk.getHeight(); } + public int getWidth() { + return _iWidth; + } + + public int getHeight() { + return _iHeight; + } + + public int getHeaderFrameNumber() { + return _bldr.getHeaderFrameNumber(); + } + public boolean addSectorIfPartOfFrame(@Nonnull ISelfDemuxingVideoSector sector) { if (!(sector instanceof IVideoSectorWithFrameNumber)) return false; @@ -81,12 +93,16 @@ public boolean isFrameComplete() { return _bldr.isFrameComplete(); } + final protected @Nonnull List getNonNullChunks(@Nonnull ILocalizedLogger log) { + return _bldr.getNonNullChunks(log); + } + public @Nonnull DemuxedFrameWithNumberAndDims finishFrame(@Nonnull ILocalizedLogger log) { - List sectors = _bldr.getNonNullChunks(log); + List sectors = getNonNullChunks(log); // need to wrap the sectors in something compatible with IReplaceableVideoSector List wrappedSectors = - new ArrayList(); + new ArrayList(sectors.size()); for (IVideoSectorWithFrameNumber vidSector : sectors) { wrappedSectors.add(new VideoSectorReplaceableDemuxPiece(vidSector)); diff --git a/jpsxdec/src/jpsxdec/modules/video/sectorbased/fps/WholeNumberSectorsPerFrame.java b/jpsxdec/src/jpsxdec/modules/video/sectorbased/fps/WholeNumberSectorsPerFrame.java index 692df61..115235c 100644 --- a/jpsxdec/src/jpsxdec/modules/video/sectorbased/fps/WholeNumberSectorsPerFrame.java +++ b/jpsxdec/src/jpsxdec/modules/video/sectorbased/fps/WholeNumberSectorsPerFrame.java @@ -178,6 +178,7 @@ else if (_startingPoints.isEmpty()) * The original Collection is not modified. */ private static @Nonnull TreeSet removeFactors(@Nonnull TreeSet sourceValues) { + @SuppressWarnings("unchecked") TreeSet copy = (TreeSet)sourceValues.clone(); for (Iterator it = copy.iterator(); it.hasNext();) { diff --git a/jpsxdec/src/jpsxdec/modules/xa/DiscItemXaAudioStream.java b/jpsxdec/src/jpsxdec/modules/xa/DiscItemXaAudioStream.java index e58739b..a1686f4 100644 --- a/jpsxdec/src/jpsxdec/modules/xa/DiscItemXaAudioStream.java +++ b/jpsxdec/src/jpsxdec/modules/xa/DiscItemXaAudioStream.java @@ -67,6 +67,7 @@ import jpsxdec.modules.sharedaudio.ISectorAudioDecoder; import jpsxdec.util.IO; import jpsxdec.util.IncompatibleException; +import jpsxdec.util.Misc; import jpsxdec.util.TaskCanceledException; /** Represents a series of XA ADPCM sectors that combine to make an audio stream. */ @@ -226,7 +227,7 @@ public int getDiscSpeed() { @Override public @Nonnull ILocalizedMessage getInterestingDescription() { - Date secs = new Date(0, 0, 0, 0, 0, Math.max((int)getApproxDuration(), 1)); + Date secs = Misc.dateFromSeconds(Math.max((int)getApproxDuration(), 1)); return I.GUI_AUDIO_DESCRIPTION(secs, _iSampleFramesPerSecond, _blnIsStereo ? 2 : 1); } diff --git a/jpsxdec/src/jpsxdec/modules/xa/SectorClaimToSectorXaAudio.java b/jpsxdec/src/jpsxdec/modules/xa/SectorClaimToSectorXaAudio.java index 2facec4..183f762 100644 --- a/jpsxdec/src/jpsxdec/modules/xa/SectorClaimToSectorXaAudio.java +++ b/jpsxdec/src/jpsxdec/modules/xa/SectorClaimToSectorXaAudio.java @@ -43,6 +43,7 @@ import javax.annotation.Nonnull; import jpsxdec.cdreaders.CdSector; import jpsxdec.cdreaders.CdSectorXaSubHeader; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.SectorClaimSystem; import jpsxdec.util.IOIterator; @@ -52,13 +53,13 @@ public class SectorClaimToSectorXaAudio extends SectorClaimSystem.SectorClaimer public interface Listener { void feedXaSector(@Nonnull CdSector cdSector, @CheckForNull SectorXaAudio xaSector, - @Nonnull ILocalizedLogger log); + @Nonnull ILocalizedLogger log) + throws LoggedFailure; void xaEof(int iChannel); public void endOfSectors(@Nonnull ILocalizedLogger log); } - @CheckForNull private final ArrayList _listeners = new ArrayList(); public SectorClaimToSectorXaAudio() { @@ -70,7 +71,7 @@ public void addListener(@CheckForNull Listener listener) { public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, @Nonnull IOIterator peekIt, @Nonnull ILocalizedLogger log) - throws IOException + throws IOException, SectorClaimSystem.ClaimerFailure { if (cs.isClaimed()) return; @@ -90,7 +91,11 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, if (sectorIsInRange(cs.getSector().getSectorIndexFromStart())) { for (Listener listener : _listeners) { - listener.feedXaSector(cdSector, xaSect, log); + try { + listener.feedXaSector(cdSector, xaSect, log); + } catch (LoggedFailure ex) { + throw new SectorClaimSystem.ClaimerFailure(ex); + } } CdSectorXaSubHeader sh = cdSector.getSubHeader(); diff --git a/jpsxdec/src/jpsxdec/modules/xa/SectorXaAudioToAudioPacket.java b/jpsxdec/src/jpsxdec/modules/xa/SectorXaAudioToAudioPacket.java index 9d86b95..d497393 100644 --- a/jpsxdec/src/jpsxdec/modules/xa/SectorXaAudioToAudioPacket.java +++ b/jpsxdec/src/jpsxdec/modules/xa/SectorXaAudioToAudioPacket.java @@ -46,6 +46,7 @@ import jpsxdec.adpcm.XaAdpcmDecoder; import jpsxdec.cdreaders.CdSector; import jpsxdec.i18n.I; +import jpsxdec.i18n.exception.LoggedFailure; import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.modules.sharedaudio.DecodedAudioPacket; import jpsxdec.util.ByteArrayFPIS; @@ -83,6 +84,7 @@ public void setListener(@CheckForNull DecodedAudioPacket.Listener listener) { public void feedXaSector(@Nonnull CdSector cdSector, @CheckForNull SectorXaAudio xaSector, @Nonnull ILocalizedLogger log) + throws LoggedFailure { if (cdSector.getSectorIndexFromStart() < _iStartSector || cdSector.getSectorIndexFromStart() > _iEndSectorInclusive) diff --git a/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr.java b/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr.java index 57309c3..c8e6685 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr.java +++ b/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr.java @@ -37,6 +37,7 @@ package jpsxdec.psxvideo; +import javax.annotation.Nonnull; import jpsxdec.formats.RGB; import jpsxdec.formats.Rec601YCbCr; @@ -93,7 +94,7 @@ public class PsxYCbCr { public PsxYCbCr() { } - public void fromRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { + public void fromRgb(@Nonnull RGB rgb1, @Nonnull RGB rgb2, @Nonnull RGB rgb3, @Nonnull RGB rgb4) { cb = cr = 0; y1 = oneRgb(rgb1); y2 = oneRgb(rgb2); @@ -102,7 +103,7 @@ public void fromRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { cb /= 4.0; cr /= 4.0; } - private double oneRgb(RGB rgb) { + private double oneRgb(@Nonnull RGB rgb) { int r_128 = rgb.getR() - 128; int g_128 = rgb.getG() - 128; int b_128 = rgb.getB() - 128; @@ -122,7 +123,7 @@ private double oneRgb(RGB rgb) { } } - public static void toRgb(double y, double cb, double cr, RGB rgb) { + public static void toRgb(double y, double cb, double cr, @Nonnull RGB rgb) { double dblChromRed, dblChromGreen, dblChromBlue; // MUST store chroma first like in instance method or result is slightly different if (INCORRECTLY_SWAP_CB_CR_LIKE_PSXMC) { @@ -142,7 +143,7 @@ public static void toRgb(double y, double cb, double cr, RGB rgb) { rgb.setB(dblYshift + dblChromBlue ); } - final public void toRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { + final public void toRgb(@Nonnull RGB rgb1, @Nonnull RGB rgb2, @Nonnull RGB rgb3, @Nonnull RGB rgb4) { double dblChromRed, dblChromGreen, dblChromBlue; if (INCORRECTLY_SWAP_CB_CR_LIKE_PSXMC) { // this math is wrong, wrong, WRONG @@ -203,7 +204,7 @@ final public void toRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { * Unfortunately this conversion doesn't take into account chroma * upsampling interpolation. */ - public void toRec_601_YCbCr(Rec601YCbCr ycc) { + public void toRec_601_YCbCr(@Nonnull Rec601YCbCr ycc) { double dblYChroma = cb * (-488509./2660418030.) + cr * (-82738./1330209015.) + 16; ycc.y1 = (y1+128)*(250./291.) + dblYChroma; @@ -215,7 +216,7 @@ public void toRec_601_YCbCr(Rec601YCbCr ycc) { ycc.cr = cb * (3673./27426990.) + cr * (8031459./9142330.) + 128; } - public void toRec_JFIF_YCbCr(Rec601YCbCr ycc) { + public void toRec_JFIF_YCbCr(@Nonnull Rec601YCbCr ycc) { double dblYChroma = cb * -3415973./13224846875. + cr * 1242172./13224846875.; ycc.y1 = y1+128 + dblYChroma; @@ -227,6 +228,7 @@ public void toRec_JFIF_YCbCr(Rec601YCbCr ycc) { ycc.cr = cb * 19492./105798775. + cr * 105791687./105798775. + 128; } + @Override public String toString() { return String.format("([%f, %f, %f, %f] %f, %f)", y1, y2, y3, y4, cb, cr); } diff --git a/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr_int.java b/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr_int.java index 383bd89..bd6f306 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr_int.java +++ b/jpsxdec/src/jpsxdec/psxvideo/PsxYCbCr_int.java @@ -37,6 +37,7 @@ package jpsxdec.psxvideo; +import javax.annotation.Nonnull; import jpsxdec.formats.RGB; import jpsxdec.util.Maths; @@ -62,7 +63,7 @@ public PsxYCbCr_int() { public static final long _0_7143 = 46812; // Math.round(0.7143 * FIXED_MULT); public static final long _1_772 = 116224; // Math.round(1.772 * FIXED_MULT)+94; - public static void toRgb(int y, int cb, int cr, RGB rgb) { + public static void toRgb(int y, int cb, int cr, @Nonnull RGB rgb) { int Yshift = y + 128; long c_r = _1_402 * cr, c_g = -_0_3437 * cb - _0_7143 * cr, @@ -72,7 +73,7 @@ public static void toRgb(int y, int cb, int cr, RGB rgb) { rgb.setB(Yshift + (int)Maths.shrRound(c_b, FIXED_BITS)); } - final public void toRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { + final public void toRgb(@Nonnull RGB rgb1, @Nonnull RGB rgb2, @Nonnull RGB rgb3, @Nonnull RGB rgb4) { int iChromRed = (int)Maths.shrRound( _1_402 * cr , FIXED_BITS); int iChromGreen = (int)Maths.shrRound( -(_0_3437 * cb) - (_0_7143 * cr), FIXED_BITS); int iChromBlue = (int)Maths.shrRound( _1_772 * cb , FIXED_BITS); @@ -99,6 +100,7 @@ final public void toRgb(RGB rgb1, RGB rgb2, RGB rgb3, RGB rgb4) { rgb4.setB(iYshift + iChromBlue); } + @Override public String toString() { return String.format( "([%d, %d, %d, %d] %d, %d)" , y1, y2, y3, y4, cb, cr); } diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamCode.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamCode.java new file mode 100644 index 0000000..0a9ad33 --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamCode.java @@ -0,0 +1,218 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.psxvideo.bitstreams; + +import javax.annotation.Nonnull; + +/** + * Specifically designed to decode the 111 MPEG1 AC coefficient + * variable-length (Huffman) bit codes, also used by the PlayStation. + * + * The codes defined in the MPEG1 spec lists the 111 codes with a 'sign' bit at + * the end. Here we split the code with the sign bit into two codes. The order + * follows the list found in the MPG1 specification with the positive sign bit + * (0) first, followed by the negative sign bit (1). Consumers of this table are + * dependent on the order of these codes. + */ +public enum BitStreamCode { + + _110______________, _111______________, // 11s + _0110_____________, _0111_____________, // 011s + _01000____________, _01001____________, // 0100s + _01010____________, _01011____________, // 0101s + _001010___________, _001011___________, // 00101s + _001100___________, _001101___________, // 00110s + _001110___________, _001111___________, // 00111s + _0001000__________, _0001001__________, // 000100s + _0001010__________, _0001011__________, // 000101s + _0001100__________, _0001101__________, // 000110s + _0001110__________, _0001111__________, // 000111s + _00001000_________, _00001001_________, // 0000100s + _00001010_________, _00001011_________, // 0000101s + _00001100_________, _00001101_________, // 0000110s + _00001110_________, _00001111_________, // 0000111s + _001000000________, _001000001________, // 00100000s + _001000010________, _001000011________, // 00100001s + _001000100________, _001000101________, // 00100010s + _001000110________, _001000111________, // 00100011s + _001001000________, _001001001________, // 00100100s + _001001010________, _001001011________, // 00100101s + _001001100________, _001001101________, // 00100110s + _001001110________, _001001111________, // 00100111s + _00000010000______, _00000010001______, // 0000001000s + _00000010010______, _00000010011______, // 0000001001s + _00000010100______, _00000010101______, // 0000001010s + _00000010110______, _00000010111______, // 0000001011s + _00000011000______, _00000011001______, // 0000001100s + _00000011010______, _00000011011______, // 0000001101s + _00000011100______, _00000011101______, // 0000001110s + _00000011110______, _00000011111______, // 0000001111s + _0000000100000____, _0000000100001____, // 000000010000s + _0000000100010____, _0000000100011____, // 000000010001s + _0000000100100____, _0000000100101____, // 000000010010s + _0000000100110____, _0000000100111____, // 000000010011s + _0000000101000____, _0000000101001____, // 000000010100s + _0000000101010____, _0000000101011____, // 000000010101s + _0000000101100____, _0000000101101____, // 000000010110s + _0000000101110____, _0000000101111____, // 000000010111s + _0000000110000____, _0000000110001____, // 000000011000s + _0000000110010____, _0000000110011____, // 000000011001s + _0000000110100____, _0000000110101____, // 000000011010s + _0000000110110____, _0000000110111____, // 000000011011s + _0000000111000____, _0000000111001____, // 000000011100s + _0000000111010____, _0000000111011____, // 000000011101s + _0000000111100____, _0000000111101____, // 000000011110s + _0000000111110____, _0000000111111____, // 000000011111s + _00000000100000___, _00000000100001___, // 0000000010000s + _00000000100010___, _00000000100011___, // 0000000010001s + _00000000100100___, _00000000100101___, // 0000000010010s + _00000000100110___, _00000000100111___, // 0000000010011s + _00000000101000___, _00000000101001___, // 0000000010100s + _00000000101010___, _00000000101011___, // 0000000010101s + _00000000101100___, _00000000101101___, // 0000000010110s + _00000000101110___, _00000000101111___, // 0000000010111s + _00000000110000___, _00000000110001___, // 0000000011000s + _00000000110010___, _00000000110011___, // 0000000011001s + _00000000110100___, _00000000110101___, // 0000000011010s + _00000000110110___, _00000000110111___, // 0000000011011s + _00000000111000___, _00000000111001___, // 0000000011100s + _00000000111010___, _00000000111011___, // 0000000011101s + _00000000111100___, _00000000111101___, // 0000000011110s + _00000000111110___, _00000000111111___, // 0000000011111s + _000000000100000__, _000000000100001__, // 00000000010000s + _000000000100010__, _000000000100011__, // 00000000010001s + _000000000100100__, _000000000100101__, // 00000000010010s + _000000000100110__, _000000000100111__, // 00000000010011s + _000000000101000__, _000000000101001__, // 00000000010100s + _000000000101010__, _000000000101011__, // 00000000010101s + _000000000101100__, _000000000101101__, // 00000000010110s + _000000000101110__, _000000000101111__, // 00000000010111s + _000000000110000__, _000000000110001__, // 00000000011000s + _000000000110010__, _000000000110011__, // 00000000011001s + _000000000110100__, _000000000110101__, // 00000000011010s + _000000000110110__, _000000000110111__, // 00000000011011s + _000000000111000__, _000000000111001__, // 00000000011100s + _000000000111010__, _000000000111011__, // 00000000011101s + _000000000111100__, _000000000111101__, // 00000000011110s + _000000000111110__, _000000000111111__, // 00000000011111s + _0000000000100000_, _0000000000100001_, // 000000000010000s + _0000000000100010_, _0000000000100011_, // 000000000010001s + _0000000000100100_, _0000000000100101_, // 000000000010010s + _0000000000100110_, _0000000000100111_, // 000000000010011s + _0000000000101000_, _0000000000101001_, // 000000000010100s + _0000000000101010_, _0000000000101011_, // 000000000010101s + _0000000000101100_, _0000000000101101_, // 000000000010110s + _0000000000101110_, _0000000000101111_, // 000000000010111s + _0000000000110000_, _0000000000110001_, // 000000000011000s + _0000000000110010_, _0000000000110011_, // 000000000011001s + _0000000000110100_, _0000000000110101_, // 000000000011010s + _0000000000110110_, _0000000000110111_, // 000000000011011s + _0000000000111000_, _0000000000111001_, // 000000000011100s + _0000000000111010_, _0000000000111011_, // 000000000011101s + _0000000000111100_, _0000000000111101_, // 000000000011110s + _0000000000111110_, _0000000000111111_, // 000000000011111s + _00000000000100000, _00000000000100001, // 0000000000010000s + _00000000000100010, _00000000000100011, // 0000000000010001s + _00000000000100100, _00000000000100101, // 0000000000010010s + _00000000000100110, _00000000000100111, // 0000000000010011s + _00000000000101000, _00000000000101001, // 0000000000010100s + _00000000000101010, _00000000000101011, // 0000000000010101s + _00000000000101100, _00000000000101101, // 0000000000010110s + _00000000000101110, _00000000000101111, // 0000000000010111s + _00000000000110000, _00000000000110001, // 0000000000011000s + _00000000000110010, _00000000000110011, // 0000000000011001s + _00000000000110100, _00000000000110101, // 0000000000011010s + _00000000000110110, _00000000000110111, // 0000000000011011s + _00000000000111000, _00000000000111001, // 0000000000011100s + _00000000000111010, _00000000000111011, // 0000000000011101s + _00000000000111100, _00000000000111101, // 0000000000011110s + _00000000000111110, _00000000000111111, // 0000000000011111s + + /** Usually "end of block" (EOB). */ + _10_______________, + /** Usually escape code. */ + _000001___________; + + // ######################################################################### + + private final String _sBitString; + + /** Bit length of this code (could use _sBitString.length(), but + * this appears to be faster). */ + private final int _iBitLength; + + private BitStreamCode() { + _sBitString = this.name().replaceAll("_", ""); + _iBitLength = _sBitString.length(); + } + + /** Returns the bits as a string. */ + public @Nonnull String getString() { + return _sBitString; + } + + public int getLength() { + return _iBitLength; + } + + // ######################################################################### + + /** Longest AC variable-length (Huffman) bit code, in bits. */ + public static final int LONGEST_BITSTREAM_CODE_17BITS = 17; + + /** Pre-load all the values into an array to avoid creating a + * new array for every call. */ + private static final BitStreamCode[] VALUES = values(); + + static { + assert VALUES.length == 111 * 2 + 2; + } + + public static @Nonnull BitStreamCode get(int i) { + return VALUES[i]; + } + + public static int getNormalCount() { + return VALUES.length - 2; + } + + public static int getTotalCount() { + return VALUES.length; + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/ObjectPool.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamDebugging.java similarity index 64% rename from jpsxdec/src/jpsxdec/util/player/ObjectPool.java rename to jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamDebugging.java index d6e25a2..f3dfd14 100644 --- a/jpsxdec/src/jpsxdec/util/player/ObjectPool.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamDebugging.java @@ -35,45 +35,46 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package jpsxdec.util.player; +package jpsxdec.psxvideo.bitstreams; -import java.util.Collection; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; -public abstract class ObjectPool { +/** Class to gather debugging info for display on each read. */ +public class BitStreamDebugging { - private int _iBalance = 0; + /** Enable to print the detailed decoding process. */ + public static boolean DEBUG = false; - @Nonnull - private final Queue _objects; - - public ObjectPool() { - _objects = new ConcurrentLinkedQueue(); + public static boolean println(@Nonnull String s) { + System.out.println(s); + return true; } - public ObjectPool(@Nonnull Collection objects) { - _objects = new ConcurrentLinkedQueue(objects); + public static long Position; + private static final StringBuilder Bits = new StringBuilder(); + + public static boolean appendBits(@Nonnull String s) { + Bits.append(s); + return true; } - @Nonnull - protected abstract T createNewObject(); + public static boolean printBitsResult(int iVectorPosition, @Nonnull MdecCode code) { + System.out.format("@%d %s -> [%d] %s", Position, Bits, iVectorPosition, code).println(); + Bits.setLength(0); + return true; + } - public @Nonnull T borrow() { - T t; - if ((t = _objects.poll()) == null) { - t = createNewObject(); - } - _iBalance++; - return t; + public static boolean setPosition(int iPosition) { + Position = iPosition; + return true; } - public void giveBack(@Nonnull T object) { - boolean blnIgnored = _objects.offer(object); // no point to wait for free space, just return - _iBalance--; - if (_iBalance == 0) { - //System.err.println("Object pool balanced."); - } + public static boolean printStartOfBlock(MdecContext context) { + System.out.println("==== Block start " + context + " ====="); + return true; } } + + diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor.java index 081aa39..2b45490 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor.java @@ -37,23 +37,17 @@ package jpsxdec.psxvideo.bitstreams; -import java.io.PrintStream; -import java.util.Arrays; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.util.BinaryDataNotRecognized; -import jpsxdec.util.Misc; /** Converts a (demuxed) video frame bitstream into an {@link MdecInputStream}, * that can then be fed into an MDEC decoder to produce an image. */ -public abstract class BitStreamUncompressor extends MdecInputStream { - - /** Enable to print the detailed decoding process. */ - public static boolean DEBUG = false; +public abstract class BitStreamUncompressor implements MdecInputStream { final static public @Nonnull BitStreamUncompressor identifyUncompressor( @Nonnull byte[] abBitstream) @@ -84,753 +78,141 @@ public abstract class BitStreamUncompressor extends MdecInputStream { throw new BinaryDataNotRecognized(); } - public enum Type { - STRv1 { - public @Nonnull BitStreamUncompressor_STRv1 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized - { - return BitStreamUncompressor_STRv1.makeV1(abBitstream, iBitstreamSize); - } - }, - STRv2 { - public @Nonnull BitStreamUncompressor_STRv2 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized - { - return BitStreamUncompressor_STRv2.makeV2(abBitstream, iBitstreamSize); - } - }, - STRv3 { - public @Nonnull BitStreamUncompressor_STRv3 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized - { - return BitStreamUncompressor_STRv3.makeV3(abBitstream, iBitstreamSize); - } - }, - Iki { - public @Nonnull BitStreamUncompressor_Iki makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized - { - return BitStreamUncompressor_Iki.makeIki(abBitstream, iBitstreamSize); - } - }, - Lain { - public @Nonnull BitStreamUncompressor_Lain makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized - { - return BitStreamUncompressor_Lain.makeLain(abBitstream, iBitstreamSize); - } - }; - - public @Nonnull BitStreamUncompressor makeNew(@Nonnull byte[] abBitstream) throws BinaryDataNotRecognized { - return makeNew(abBitstream, abBitstream.length); - } + // ######################################################################### - abstract public @Nonnull BitStreamUncompressor makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) - throws BinaryDataNotRecognized; + public interface IAcEscapeCode { + /** Read an AC Coefficient escaped (zero-run, AC level) value. */ + void readAcEscapeCode(@Nonnull ArrayBitReader bitReader, @Nonnull MdecCode code) + throws MdecException.EndOfStream; } - /** Longest AC variable-length (Huffman) bit code, in bits. */ - public final static int AC_LONGEST_VARIABLE_LENGTH_CODE = 17; - - /** Holds the mapping of bit string to Zero-Run Length + AC Coefficient. */ - protected static class AcBitCode { - /** Readable string of bits of this code. */ - @Nonnull - final public String BitString; - /** Number of AC coefficient zero values, - * or Integer.MIN_VALUE if N/A. */ - final public int ZeroRun; - /** Non-zero AC coefficient value (may be an absolute value), - * or Integer.MIN_VALUE if N/A. */ - final public int AcCoefficient; - /** Bit length of this code (could use Bits.length(), but - * this appears to be faster). */ - final public int BitLength; - - /** Create a bit code with valid {@link #Run} and {@link Ac} values. */ - private AcBitCode(@Nonnull String sBitString, int iZeroRun, int iAcCoefficient) { - BitString = sBitString; - ZeroRun = iZeroRun; - AcCoefficient = iAcCoefficient; - BitLength = sBitString.length(); - } - - @Override - public String toString() { - return BitString + "=(" + ZeroRun + ", " + AcCoefficient + ")"; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || getClass() != obj.getClass()) - return false; - final AcBitCode other = (AcBitCode) obj; - return this.ZeroRun == other.ZeroRun && this.AcCoefficient == other.AcCoefficient; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 79 * hash + this.ZeroRun; - hash = 79 * hash + this.AcCoefficient; - return hash; - } + public interface IQuantizationDc { + /** Read the quantization scale and DC coefficient from the bitstream. */ + void readQuantizationScaleAndDc(@Nonnull ArrayBitReader bitReader, + @Nonnull MdecContext context, + @Nonnull MdecCode out) + throws MdecException.ReadCorruption, MdecException.EndOfStream; } - /** A bit code that doesn't have meaningful - * {@link #ZeroRun} and {@link #AcCoefficient} values. - * Superclass fields are effectively ignored. */ - private static class SpecialAcBitCode extends AcBitCode { - - @Nonnull - private final String _sId; - - private SpecialAcBitCode(@Nonnull String sBitString, @Nonnull String sId) { - super(sBitString, Integer.MIN_VALUE, Integer.MIN_VALUE); - _sId = sId; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || getClass() != obj.getClass()) - return false; - final SpecialAcBitCode other = (SpecialAcBitCode) obj; - // no need to check superclass fields since the id is the only important value - return Misc.objectEquals(_sId, other._sId); - } - - @Override - public int hashCode() { - int hash = 5; - hash = 89 * hash + (_sId != null ? _sId.hashCode() : 0); - return hash; - } - - @Override - public String toString() { - return BitString + "=" + _sId; - } + public interface IFrameEndPaddingBits { + void skipPaddingBits(@Nonnull ArrayBitReader bitReader) throws MdecException.EndOfStream; } - - /** Blazing fast bitstream parser. - * Specifically designed to decode the 111 PSX (and MPEG1) AC coefficient - * variable-length (Huffman) bit codes, including the {@link #END_OF_BLOCK} - * code and {@link #ESCAPE_CODE}. Every method beginning with - * underscore ('_') should be called before the class is used for parsing. - */ - protected static final class AcLookup { - - /** Sequence of bits indicating the end of a block. */ - public final static AcBitCode END_OF_BLOCK = new SpecialAcBitCode("10", "EOB"); - /** Sequence of bits indicating an escape code. */ - public final static AcBitCode ESCAPE_CODE = new SpecialAcBitCode("000001", "ESCAPE_CODE"); - - /** Bit codes for '11s'. Can be used as an alternative to the - * {@link #Table_1xx} table. */ - private AcBitCode _110, _111; - - /** Table to look up END_OF_BLOCK ('10') and 11s codes using all but the first (1) bit. */ - private final AcBitCode[] Table_1xx = new AcBitCode[4]; - /** Table to look up codes '011s' to '00100111s' using all but the first (0) bit. - * Given a bit code in that range, strip the leading zero bit, then pad - * any extra trailing bits to make an 8 bit value. Use that value as the - * index in this table to get the corresponding code. */ - private final AcBitCode[] Table_0xxxxxxx = new AcBitCode[256]; - /** Table to look up codes '0000001000s' to '0000000011111s' using all but - * the first 6 zero bits. - * Given a bit code in that range, strip the leading 6 zero bits, then pad - * any extra trailing bits to make an 8 bit value. Use that value as the - * index in this table to get the corresponding code. */ - private final AcBitCode[] Table_000000xxxxxxxx = new AcBitCode[256]; - /** Table to look up codes '00000000010000s' to '0000000000011111s' using - * all but the first 9 zero bits. - * Given a bit code in that range, strip the leading 9 zero bits, then pad - * any extra trailing bits to make an 8 bit value. Use that value as the - * index in this table to get the corresponding code. */ - private final AcBitCode[] Table_000000000xxxxxxxx = new AcBitCode[256]; - - /** Holds all the codes for references and compression. */ - private final AcBitCode[] _aoAcBitCodes = new AcBitCode[111]; - - public AcLookup() { - // initialize the two codes we know about - setBits(END_OF_BLOCK); - setBits(ESCAPE_CODE); - } - - // - public AcLookup _11s(int r, int aac) { - set(0, "11s", r, aac); - _110 = new AcBitCode("110", r, aac); - _111 = new AcBitCode("111", r, -aac); - return this; - } - // - public AcLookup _011s (int r, int aac) { return set( 1, "011s", r, aac); } - public AcLookup _0100s (int r, int aac) { return set( 2, "0100s", r, aac); } - public AcLookup _0101s (int r, int aac) { return set( 3, "0101s", r, aac); } - public AcLookup _00101s (int r, int aac) { return set( 4, "00101s", r, aac); } - public AcLookup _00110s (int r, int aac) { return set( 5, "00110s", r, aac); } - public AcLookup _00111s (int r, int aac) { return set( 6, "00111s", r, aac); } - public AcLookup _000100s (int r, int aac) { return set( 7, "000100s", r, aac); } - public AcLookup _000101s (int r, int aac) { return set( 8, "000101s", r, aac); } - public AcLookup _000110s (int r, int aac) { return set( 9, "000110s", r, aac); } - public AcLookup _000111s (int r, int aac) { return set( 10, "000111s", r, aac); } - public AcLookup _0000100s (int r, int aac) { return set( 11, "0000100s", r, aac); } - public AcLookup _0000101s (int r, int aac) { return set( 12, "0000101s", r, aac); } - public AcLookup _0000110s (int r, int aac) { return set( 13, "0000110s", r, aac); } - public AcLookup _0000111s (int r, int aac) { return set( 14, "0000111s", r, aac); } - public AcLookup _00100000s(int r, int aac) { return set( 15, "00100000s", r, aac); } - public AcLookup _00100001s(int r, int aac) { return set( 16, "00100001s", r, aac); } - public AcLookup _00100010s(int r, int aac) { return set( 17, "00100010s", r, aac); } - public AcLookup _00100011s(int r, int aac) { return set( 18, "00100011s", r, aac); } - public AcLookup _00100100s(int r, int aac) { return set( 19, "00100100s", r, aac); } - public AcLookup _00100101s(int r, int aac) { return set( 20, "00100101s", r, aac); } - public AcLookup _00100110s(int r, int aac) { return set( 21, "00100110s", r, aac); } - public AcLookup _00100111s(int r, int aac) { return set( 22, "00100111s", r, aac); } - // - // ------------------------------------------------------------------------------ - // - public AcLookup _0000001000s(int r, int aac) { return set( 23, "0000001000s", r, aac); } - public AcLookup _0000001001s(int r, int aac) { return set( 24, "0000001001s", r, aac); } - public AcLookup _0000001010s(int r, int aac) { return set( 25, "0000001010s", r, aac); } - public AcLookup _0000001011s(int r, int aac) { return set( 26, "0000001011s", r, aac); } - public AcLookup _0000001100s(int r, int aac) { return set( 27, "0000001100s", r, aac); } - public AcLookup _0000001101s(int r, int aac) { return set( 28, "0000001101s", r, aac); } - public AcLookup _0000001110s(int r, int aac) { return set( 29, "0000001110s", r, aac); } - public AcLookup _0000001111s(int r, int aac) { return set( 30, "0000001111s", r, aac); } - public AcLookup _000000010000s(int r, int aac) { return set( 31, "000000010000s", r, aac); } - public AcLookup _000000010001s(int r, int aac) { return set( 32, "000000010001s", r, aac); } - public AcLookup _000000010010s(int r, int aac) { return set( 33, "000000010010s", r, aac); } - public AcLookup _000000010011s(int r, int aac) { return set( 34, "000000010011s", r, aac); } - public AcLookup _000000010100s(int r, int aac) { return set( 35, "000000010100s", r, aac); } - public AcLookup _000000010101s(int r, int aac) { return set( 36, "000000010101s", r, aac); } - public AcLookup _000000010110s(int r, int aac) { return set( 37, "000000010110s", r, aac); } - public AcLookup _000000010111s(int r, int aac) { return set( 38, "000000010111s", r, aac); } - public AcLookup _000000011000s(int r, int aac) { return set( 39, "000000011000s", r, aac); } - public AcLookup _000000011001s(int r, int aac) { return set( 40, "000000011001s", r, aac); } - public AcLookup _000000011010s(int r, int aac) { return set( 41, "000000011010s", r, aac); } - public AcLookup _000000011011s(int r, int aac) { return set( 42, "000000011011s", r, aac); } - public AcLookup _000000011100s(int r, int aac) { return set( 43, "000000011100s", r, aac); } - public AcLookup _000000011101s(int r, int aac) { return set( 44, "000000011101s", r, aac); } - public AcLookup _000000011110s(int r, int aac) { return set( 45, "000000011110s", r, aac); } - public AcLookup _000000011111s(int r, int aac) { return set( 46, "000000011111s", r, aac); } - public AcLookup _0000000010000s(int r, int aac) { return set( 47, "0000000010000s", r, aac); } - public AcLookup _0000000010001s(int r, int aac) { return set( 48, "0000000010001s", r, aac); } - public AcLookup _0000000010010s(int r, int aac) { return set( 49, "0000000010010s", r, aac); } - public AcLookup _0000000010011s(int r, int aac) { return set( 50, "0000000010011s", r, aac); } - public AcLookup _0000000010100s(int r, int aac) { return set( 51, "0000000010100s", r, aac); } - public AcLookup _0000000010101s(int r, int aac) { return set( 52, "0000000010101s", r, aac); } - public AcLookup _0000000010110s(int r, int aac) { return set( 53, "0000000010110s", r, aac); } - public AcLookup _0000000010111s(int r, int aac) { return set( 54, "0000000010111s", r, aac); } - public AcLookup _0000000011000s(int r, int aac) { return set( 55, "0000000011000s", r, aac); } - public AcLookup _0000000011001s(int r, int aac) { return set( 56, "0000000011001s", r, aac); } - public AcLookup _0000000011010s(int r, int aac) { return set( 57, "0000000011010s", r, aac); } - public AcLookup _0000000011011s(int r, int aac) { return set( 58, "0000000011011s", r, aac); } - public AcLookup _0000000011100s(int r, int aac) { return set( 59, "0000000011100s", r, aac); } - public AcLookup _0000000011101s(int r, int aac) { return set( 60, "0000000011101s", r, aac); } - public AcLookup _0000000011110s(int r, int aac) { return set( 61, "0000000011110s", r, aac); } - public AcLookup _0000000011111s(int r, int aac) { return set( 62, "0000000011111s", r, aac); } - // - // --------------------------------------------------------------------------------------- - // - public AcLookup _00000000010000s(int r, int aac) { return set( 63, "00000000010000s", r, aac); } - public AcLookup _00000000010001s(int r, int aac) { return set( 64, "00000000010001s", r, aac); } - public AcLookup _00000000010010s(int r, int aac) { return set( 65, "00000000010010s", r, aac); } - public AcLookup _00000000010011s(int r, int aac) { return set( 66, "00000000010011s", r, aac); } - public AcLookup _00000000010100s(int r, int aac) { return set( 67, "00000000010100s", r, aac); } - public AcLookup _00000000010101s(int r, int aac) { return set( 68, "00000000010101s", r, aac); } - public AcLookup _00000000010110s(int r, int aac) { return set( 69, "00000000010110s", r, aac); } - public AcLookup _00000000010111s(int r, int aac) { return set( 70, "00000000010111s", r, aac); } - public AcLookup _00000000011000s(int r, int aac) { return set( 71, "00000000011000s", r, aac); } - public AcLookup _00000000011001s(int r, int aac) { return set( 72, "00000000011001s", r, aac); } - public AcLookup _00000000011010s(int r, int aac) { return set( 73, "00000000011010s", r, aac); } - public AcLookup _00000000011011s(int r, int aac) { return set( 74, "00000000011011s", r, aac); } - public AcLookup _00000000011100s(int r, int aac) { return set( 75, "00000000011100s", r, aac); } - public AcLookup _00000000011101s(int r, int aac) { return set( 76, "00000000011101s", r, aac); } - public AcLookup _00000000011110s(int r, int aac) { return set( 77, "00000000011110s", r, aac); } - public AcLookup _00000000011111s(int r, int aac) { return set( 78, "00000000011111s", r, aac); } - public AcLookup _000000000010000s(int r, int aac) { return set( 79, "000000000010000s", r, aac); } - public AcLookup _000000000010001s(int r, int aac) { return set( 80, "000000000010001s", r, aac); } - public AcLookup _000000000010010s(int r, int aac) { return set( 81, "000000000010010s", r, aac); } - public AcLookup _000000000010011s(int r, int aac) { return set( 82, "000000000010011s", r, aac); } - public AcLookup _000000000010100s(int r, int aac) { return set( 83, "000000000010100s", r, aac); } - public AcLookup _000000000010101s(int r, int aac) { return set( 84, "000000000010101s", r, aac); } - public AcLookup _000000000010110s(int r, int aac) { return set( 85, "000000000010110s", r, aac); } - public AcLookup _000000000010111s(int r, int aac) { return set( 86, "000000000010111s", r, aac); } - public AcLookup _000000000011000s(int r, int aac) { return set( 87, "000000000011000s", r, aac); } - public AcLookup _000000000011001s(int r, int aac) { return set( 88, "000000000011001s", r, aac); } - public AcLookup _000000000011010s(int r, int aac) { return set( 89, "000000000011010s", r, aac); } - public AcLookup _000000000011011s(int r, int aac) { return set( 90, "000000000011011s", r, aac); } - public AcLookup _000000000011100s(int r, int aac) { return set( 91, "000000000011100s", r, aac); } - public AcLookup _000000000011101s(int r, int aac) { return set( 92, "000000000011101s", r, aac); } - public AcLookup _000000000011110s(int r, int aac) { return set( 93, "000000000011110s", r, aac); } - public AcLookup _000000000011111s(int r, int aac) { return set( 94, "000000000011111s", r, aac); } - public AcLookup _0000000000010000s(int r, int aac) { return set( 95, "0000000000010000s", r, aac); } - public AcLookup _0000000000010001s(int r, int aac) { return set( 96, "0000000000010001s", r, aac); } - public AcLookup _0000000000010010s(int r, int aac) { return set( 97, "0000000000010010s", r, aac); } - public AcLookup _0000000000010011s(int r, int aac) { return set( 98, "0000000000010011s", r, aac); } - public AcLookup _0000000000010100s(int r, int aac) { return set( 99, "0000000000010100s", r, aac); } - public AcLookup _0000000000010101s(int r, int aac) { return set(100, "0000000000010101s", r, aac); } - public AcLookup _0000000000010110s(int r, int aac) { return set(101, "0000000000010110s", r, aac); } - public AcLookup _0000000000010111s(int r, int aac) { return set(102, "0000000000010111s", r, aac); } - public AcLookup _0000000000011000s(int r, int aac) { return set(103, "0000000000011000s", r, aac); } - public AcLookup _0000000000011001s(int r, int aac) { return set(104, "0000000000011001s", r, aac); } - public AcLookup _0000000000011010s(int r, int aac) { return set(105, "0000000000011010s", r, aac); } - public AcLookup _0000000000011011s(int r, int aac) { return set(106, "0000000000011011s", r, aac); } - public AcLookup _0000000000011100s(int r, int aac) { return set(107, "0000000000011100s", r, aac); } - public AcLookup _0000000000011101s(int r, int aac) { return set(108, "0000000000011101s", r, aac); } - public AcLookup _0000000000011110s(int r, int aac) { return set(109, "0000000000011110s", r, aac); } - public AcLookup _0000000000011111s(int r, int aac) { return set(110, "0000000000011111s", r, aac); } - // - // - - /** Given the AC coefficient absolute value, creates both positive and negative - * versions and assigns them to appropriate lookup tables. Also adds - * the absolute value version to the reference list of codes. */ - private AcLookup set(int iCodeNum, String sBits, int iRun, int iAbsoluteAc) { - _aoAcBitCodes[iCodeNum] = new AcBitCode(sBits, iRun, iAbsoluteAc); - setBits(new AcBitCode(sBits.replace('s', '0'), iRun, iAbsoluteAc)); - setBits(new AcBitCode(sBits.replace('s', '1'), iRun, -iAbsoluteAc)); - return this; - } - - /** Identifies the lookup table in which to place the bit code. */ - private void setBits(AcBitCode lu) { - final int iBitsRemain; - final AcBitCode[] aoTable; - final int iTableStart; - - if (lu.BitString.startsWith("000000000")) { - aoTable = Table_000000000xxxxxxxx; - iBitsRemain = 8 - (lu.BitString.length() - 9); - iTableStart = Integer.parseInt(lu.BitString, 2) << iBitsRemain; - } else if (lu.BitString.startsWith("000000" )) { - aoTable = Table_000000xxxxxxxx; - iBitsRemain = 8 - (lu.BitString.length() - 6); - iTableStart = Integer.parseInt(lu.BitString, 2) << iBitsRemain; - } else if (lu.BitString.startsWith("0" )) { - aoTable = Table_0xxxxxxx; - iBitsRemain = 8 - (lu.BitString.length() - 1); - iTableStart = Integer.parseInt(lu.BitString, 2) << iBitsRemain; - } else { // startsWith("1") - aoTable = Table_1xx; - iBitsRemain = 2 - (lu.BitString.length() - 1); - iTableStart = Integer.parseInt(lu.BitString.substring(1), 2) << iBitsRemain; - } - - final int iTableEntriesToAssociate = (1 << iBitsRemain); - for (int i = 0; i < iTableEntriesToAssociate; i++) { - if (aoTable[iTableStart + i] != null) - throw new RuntimeException("Resetting an existing bitstream lookup probably means some code is wrong."); - aoTable[iTableStart + i] = lu; - } - } - - public @Nonnull Iterable getCodeList() { - return Arrays.asList(_aoAcBitCodes); - } - - /** Useful bitmasks. */ - private static final int - b11000000000000000 = 0x18000, - b10000000000000000 = 0x10000, - b01000000000000000 = 0x08000, - b00100000000000000 = 0x04000, - b01111100000000000 = 0x0F800, - b00000011100000000 = 0x00700, - b00000000011100000 = 0x000E0; - - /** Converts bits to the equivalent {@link BitstreamToMdecLookup}. - * 17 bits need to be supplied to decode (the longest bit code). - * Bits should start at the least-significant bit. - * Bits beyond the 17 least-significant bits are ignored. - * If a full 17 bits are unavailable, fill the remaining with zeros - * to ensure failure if bit code is invalid. - * - * @param i17bits Integer containing 17 bits to decode. - */ - private @Nonnull AcBitCode lookup(final int i17bits) throws MdecException.ReadCorruption { - if ((i17bits & b10000000000000000) != 0) { - assert !DEBUG || debugPrintln("Table 0 offset " + ((i17bits >> 14) & 3)); - return Table_1xx[(i17bits >> 14) & 3]; - } else if ((i17bits & b01111100000000000) != 0) { - assert !DEBUG || debugPrintln("Table 1 offset " + ((i17bits >> 8) & 0xff)); - return Table_0xxxxxxx[(i17bits >> 8) & 0xff]; - } else if ((i17bits & b00000011100000000) != 0) { - assert !DEBUG || debugPrintln("Table 2 offset " + ((i17bits >> 3) & 0xff)); - return Table_000000xxxxxxxx[(i17bits >> 3) & 0xff]; - } else if ((i17bits & b00000000011100000) != 0) { - assert !DEBUG || debugPrintln("Table 3 offset " + (i17bits & 0xff)); - return Table_000000000xxxxxxxx[i17bits & 0xff]; - } else { - throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); - } - } - private static String UNMATCHED_AC_VLC(int i17bits) { - return "Unmatched AC variable length code: " + - Misc.bitsToString(i17bits, AC_LONGEST_VARIABLE_LENGTH_CODE); - } - - // .................................................. - - // - /** Alternative lookup method that is slower that {@link #lookup(int)}. */ - private AcBitCode lookup_slow1(final int i17bits) throws MdecException.ReadCorruption { - if ((i17bits & b11000000000000000) == b10000000000000000) { - return END_OF_BLOCK; - } else if ((i17bits & b11000000000000000) == b11000000000000000) { - // special handling for first AC code - if ((i17bits & b00100000000000000) == 0) // check sign bit - return _110; - else - return _111; - } else { - // escape code is already set in the lookup table - final AcBitCode[] array; - final int c; - if ((i17bits & b01111100000000000) != 0) { - array = Table_0xxxxxxx; - c = (i17bits >> 8) & 0xff; - } else if ((i17bits & b00000011100000000) != 0) { - array = Table_000000xxxxxxxx; - c = (i17bits >> 3) & 0xff; - } else if ((i17bits & b00000000011100000) != 0) { - array = Table_000000000xxxxxxxx; - c = i17bits & 0xff; - } else { - throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); - } - return array[c]; - } - } - /** Alternative lookup method that is slower that {@link #lookup(int)}. */ - private AcBitCode lookup_slow2(final int i17bits) throws MdecException.ReadCorruption { - if ((i17bits & b11000000000000000) == b10000000000000000) { - return END_OF_BLOCK; - } else if ((i17bits & b11000000000000000) == b11000000000000000) { - // special handling for first AC code - if ((i17bits & b00100000000000000) == 0) // check sign bit - return _110; - else - return _111; - } else { - // escape code is already part of the lookup table - if ((i17bits & b01111100000000000) != 0) { - return Table_0xxxxxxx[(i17bits >> 8) & 0xff]; - } else if ((i17bits & b00000011100000000) != 0) { - return Table_000000xxxxxxxx[(i17bits >> 3) & 0xff]; - } else if ((i17bits & b00000000011100000) != 0) { - return Table_000000000xxxxxxxx[i17bits & 0xff]; - } else { - throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); - } - } - } - /** Alternative lookup method that is slower that {@link #lookup(int)}. */ - private AcBitCode lookup_slow3(final int i17bits) throws MdecException.ReadCorruption { - if ((i17bits & b10000000000000000) != 0) { // 1st bit is 1? - if ((i17bits & b01000000000000000) == 0) { // initial bits == '10'? - return END_OF_BLOCK; - } else { // initial bits == '11' - // special handling for first AC code - // check sign bit - if ((i17bits & b00100000000000000) == 0) // initial bits == '110'? - return _110; - else // initial bits == '111' - return _111; - } - } else { // 1st bit is 0 - // escape code is already set in the lookup table - final AcBitCode[] array; - final int c; - if ((i17bits & b01111100000000000) != 0) { - array = Table_0xxxxxxx; - c = (i17bits >> 8) & 0xff; - } else if ((i17bits & b00000011100000000) != 0) { - array = Table_000000xxxxxxxx; - c = (i17bits >> 3) & 0xff; - } else if ((i17bits & b00000000011100000) != 0) { - array = Table_000000000xxxxxxxx; - c = i17bits & 0xff; - } else { - throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); - } - return array[c]; - } - } - - /** Bit masks for - * {@link #lookup_old(int, jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode)} */ - private static final int - b1000000000000000_ = 0x8000 << 1, - b0100000000000000_ = 0x4000 << 1, - b0010000000000000_ = 0x2000 << 1, - b0001100000000000_ = 0x1800 << 1, - b0001000000000000_ = 0x1000 << 1, - b0000100000000000_ = 0x0800 << 1, - b0000010000000000_ = 0x0400 << 1, - b0000001000000000_ = 0x0200 << 1, - b0000000100000000_ = 0x0100 << 1, - b0000000010000000_ = 0x0080 << 1, - b0000000001000000_ = 0x0040 << 1, - b0000000000100000_ = 0x0020 << 1, - b0000000000010000_ = 0x0010 << 1; - /** This lookup approach used in 0.96.0 and earlier needs a slightly - * different API to work. The calling code will need some adjustment. */ - private AcBitCode lookup_old(final int i17bits, MdecCode code) throws MdecException.ReadCorruption { - - final AcBitCode vlc; - - // Walk through the bits, one-by-one - // Fun fact: The Lain Playstation game uses this same decoding approach - if ( (i17bits & b1000000000000000_) != 0) { // "1" - if ((i17bits & b0100000000000000_) != 0) { // "11" - vlc = _aoAcBitCodes[0]; - } else { // "10" - // End of block - code.setToEndOfData(); - return END_OF_BLOCK; - } - } else if ((i17bits & b0100000000000000_) != 0) { // "01" - if ((i17bits & b0010000000000000_) != 0) { // "011" - vlc = _aoAcBitCodes[1]; - } else { // "010x" - vlc = _aoAcBitCodes[2 + (int)((i17bits >>> 13) & 1)]; - } - } else if ((i17bits & b0010000000000000_) != 0) { // "001" - if ((i17bits & b0001100000000000_) != 0) { // "001xx" - vlc = _aoAcBitCodes[3 + (int)((i17bits >>> 12) & 3)]; - } else { // "00100xxx" - vlc = _aoAcBitCodes[15 + (int)((i17bits >>> 9) & 7)]; - } - } else if ((i17bits & b0001000000000000_) != 0) { // "0001xx" - vlc = _aoAcBitCodes[7 + (int)((i17bits >>> 11) & 3)]; - } else if ((i17bits & b0000100000000000_) != 0) { // "00001xx" - vlc = _aoAcBitCodes[11 + (int)((i17bits >>> 10) & 3)]; - } else if ((i17bits & b0000010000000000_) != 0) { // "000001" - // escape code - return ESCAPE_CODE; - } else if ((i17bits & b0000001000000000_) != 0) { // "0000001xxx" - vlc = _aoAcBitCodes[23 + (int)((i17bits >>> 7) & 7)]; - } else if ((i17bits & b0000000100000000_) != 0) { // "00000001xxxx" - vlc = _aoAcBitCodes[31 + (int)((i17bits >>> 5) & 15)]; - } else if ((i17bits & b0000000010000000_) != 0) { // "000000001xxxx" - vlc = _aoAcBitCodes[47 + (int)((i17bits >>> 4) & 15)]; - } else if ((i17bits & b0000000001000000_) != 0) { // "0000000001xxxx" - vlc = _aoAcBitCodes[63 + (int)((i17bits >>> 3) & 15)]; - } else if ((i17bits & b0000000000100000_) != 0) { // "00000000001xxxx" - vlc = _aoAcBitCodes[79 + (int)((i17bits >>> 2) & 15)]; - } else if ((i17bits & b0000000000010000_) != 0) { // "000000000001xxxx" - vlc = _aoAcBitCodes[95 + (int)((i17bits >>> 1) & 15)]; - } else { - throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); - } - - code.setTop6Bits(vlc.ZeroRun); - - // Take either the positive or negitive AC coefficient, - // depending on the sign bit - if ((i17bits & (1 << (16 - vlc.BitLength))) == 0) { - // positive - code.setBottom10Bits(vlc.AcCoefficient); - } else { - // negative - code.setBottom10Bits(-vlc.AcCoefficient); - } - - return vlc; - } - // - - public void print(@Nonnull PrintStream ps) { - for (int i = 0; i < Table_1xx.length; i++) { - ps.println("Table_1xx["+i+"] = "+Table_1xx[i]); - } - for (int i = 0; i < Table_0xxxxxxx.length; i++) { - ps.println("Table_0xxxxxxx["+i+"] = "+Table_0xxxxxxx[i]); - } - for (int i = 0; i < Table_000000xxxxxxxx.length; i++) { - ps.println("Table_000000xxxxxxxx["+i+"] = "+Table_000000xxxxxxxx[i]); - } - for (int i = 0; i < Table_000000000xxxxxxxx.length; i++) { - ps.println("Table_000000000xxxxxxxx["+i+"] = "+Table_000000000xxxxxxxx[i]); - } + private static class FrameEndPaddingBits_None implements IFrameEndPaddingBits { + public void skipPaddingBits(@Nonnull ArrayBitReader bitReader) { } } - private static boolean debugPrintln(@Nonnull String s) { - System.out.println(s); - return true; - } + public static final IFrameEndPaddingBits FRAME_END_PADDING_BITS_NONE = new FrameEndPaddingBits_None(); - /** Class to gather debugging info for display on each read. */ - public static class MdecDebugger { - public long Position; - private final StringBuilder Bits = new StringBuilder(); - public boolean append(@Nonnull String s) { - Bits.append(s); - return true; - } - public boolean print(int iVectorPosition, @Nonnull MdecCode code) { - System.out.format("@%d %s -> [%d] %s", Position, Bits, iVectorPosition, code).println(); - Bits.setLength(0); - return true; - } - - public boolean setPosition(int iPosition) { - Position = iPosition; - return true; - } + // ######################################################################### - private boolean printBlock(int iCurrentMacroBlock, int iCurrentMacroBlockSubBlock) { - System.out.format("==== MB %d Block %d ====", iCurrentMacroBlock, iCurrentMacroBlockSubBlock).println(); - return true; - } - } + /** Binary input stream being read. */ + @Nonnull + private final ArrayBitReader _bitReader; - /** A strange value needed for video bitstreams and video sector headers. - * It's the number of MDEC codes, divided by two, then rounded up to the - * next closest multiple of 32 (if not already a multiple of 32). - * In other words, its the number of 32-byte blocks it would take to hold - * the MDEC codes. */ - public static short calculateHalfCeiling32(int iMdecCodeCount) { - return (short) ((((iMdecCodeCount + 1) / 2) + 31) & ~31); - } + /** Table for looking up AC Coefficient bit codes. */ + @Nonnull + private final ZeroRunLengthAcLookup _lookupTable; - // ######################################################################### - // ######################################################################### + @Nonnull + private final IQuantizationDc _qscaleDcReader; - /** Table for looking up AC Coefficient bit codes. */ @Nonnull - private final AcLookup _lookupTable; - /** Binary input stream being read. */ + private final IAcEscapeCode _escapeCodeReader; + @Nonnull - protected final ArrayBitReader _bitReader; - - /** Holds the debugger when debugging is enabled. */ - @CheckForNull - protected final MdecDebugger _debug; - - /** Indicates if the next read will be the Quantization Scale and DC Coefficient. */ - private boolean _blnBlockStart; - /** Indicates if the next read will be the Quantization Scale and DC Coefficient. */ - protected boolean atStartOfBlock() { return _blnBlockStart; } - /** Number of the current Macro Block being read. */ - private int _iCurrentMacroBlock; - /** Number of the current Macro Block being read. */ - protected int getFullMacroBlocksRead() { return _iCurrentMacroBlock; } - /** 0 to 5 to indicate the current Macro Block's sub-block being read. */ - private int _iCurrentMacroBlockSubBlock; - /** Returns 0 to 5 to indicate the current Macro Block's sub-block being read. */ - protected int getCurrentMacroBlockSubBlock() { return _iCurrentMacroBlockSubBlock; } + private final IFrameEndPaddingBits _endPaddingBits; + + protected final MdecContext _context = new MdecContext(); + /** Track the current Block's vector position to detect errors. */ - private int _iCurrentBlockVectorPos; + private int _iCurrentBlockVectorPos = 0; /** Number of MDEC codes that have been read thus far. */ - private int _iReadMdecCodeCount; - /** Number of MDEC codes that have been read thus far. */ - public int getReadMdecCodeCount() { return _iReadMdecCodeCount; } + public int getReadMdecCodeCount() { return _context.getTotalMdecCodesRead(); } - protected BitStreamUncompressor(@Nonnull AcLookup lookupTable, - @Nonnull ArrayBitReader bitReader) + public BitStreamUncompressor(@Nonnull ArrayBitReader bitReader, + @Nonnull ZeroRunLengthAcLookup lookupTable, + @Nonnull IQuantizationDc qscaleDcReader, + @Nonnull IAcEscapeCode escapeCodeReader, + @Nonnull IFrameEndPaddingBits endPaddingBits) { + _bitReader = bitReader; _lookupTable = lookupTable; - if (DEBUG) - _debug = new MdecDebugger(); - else - _debug = null; + _qscaleDcReader = qscaleDcReader; + _escapeCodeReader = escapeCodeReader; + _endPaddingBits = endPaddingBits; + } - _bitReader = bitReader; - - _blnBlockStart = true; - _iReadMdecCodeCount = 0; - _iCurrentBlockVectorPos = 0; - _iCurrentMacroBlock = 0; - _iCurrentMacroBlockSubBlock = 0; + final public int getBitPosition() { + return _bitReader.getBitsRead(); } - /** @throws NullPointerException if {@code reset()} has not been called. */ final public boolean readMdecCode(@Nonnull MdecCode code) throws MdecException.EndOfStream, MdecException.ReadCorruption { - assert !DEBUG || _debug.setPosition(_bitReader.getWordPosition()); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.setPosition(_bitReader.getWordPosition()); - if (_blnBlockStart) { - assert !DEBUG || _debug.printBlock(_iCurrentMacroBlock, _iCurrentMacroBlockSubBlock); + if (_context.atStartOfBlock()) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.printStartOfBlock(_context); - readQscaleAndDC(code); - _blnBlockStart = false; + _qscaleDcReader.readQuantizationScaleAndDc(_bitReader, _context, code); + _context.nextCode(); } else { - int i17bits = _bitReader.peekUnsignedBits(AC_LONGEST_VARIABLE_LENGTH_CODE); - AcBitCode bitCode = _lookupTable.lookup(i17bits); - _bitReader.skipBits(bitCode.BitLength); + int i17bits = _bitReader.peekUnsignedBits(BitStreamCode.LONGEST_BITSTREAM_CODE_17BITS); + ZeroRunLengthAc bitCode = _lookupTable.lookup(i17bits); + _bitReader.skipBits(bitCode.getBitLength()); + + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(bitCode.getBitString()); - if (bitCode == AcLookup.END_OF_BLOCK) { + if (bitCode.isIsEndOfBlock()) { // end of block code.setToEndOfData(); - _blnBlockStart = true; _iCurrentBlockVectorPos = 0; - _iCurrentMacroBlockSubBlock++; - if (_iCurrentMacroBlockSubBlock >= 6) { - _iCurrentMacroBlockSubBlock = 0; - _iCurrentMacroBlock++; - } - assert !DEBUG || _debug.append(AcLookup.END_OF_BLOCK.BitString); + _context.nextCodeEndBlock(); } else { // block continues - assert !DEBUG || _debug.append(bitCode.BitString); - if (bitCode == AcLookup.ESCAPE_CODE) { - readEscapeAcCode(code); + if (bitCode.isIsEscapeCode()) { + _escapeCodeReader.readAcEscapeCode(_bitReader, code); } else { - code.setBits(bitCode.ZeroRun, bitCode.AcCoefficient); + bitCode.getMdecCode(code); } _iCurrentBlockVectorPos += code.getTop6Bits() + 1; if (_iCurrentBlockVectorPos >= 64) { - throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_MB_BLOCK(_iCurrentBlockVectorPos, _iCurrentMacroBlock, _iCurrentMacroBlockSubBlock)); + throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_MB_BLOCK(_iCurrentBlockVectorPos, _context.getTotalMacroBlocksRead(), _context.getCurrentBlock().ordinal())); } - } + _context.nextCode(); + } } - - assert !DEBUG || _debug.print(_iCurrentBlockVectorPos, code); - // _blnBlockStart will be set to true if an EOB code was read - _iReadMdecCodeCount++; - return _blnBlockStart; - } + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.printBitsResult(_iCurrentBlockVectorPos, code); - final public int getBitPosition() { - return _bitReader.getBitsRead(); + return _context.atStartOfBlock(); } /** Skips macroblocks that would fit within the given dimensions. */ - public void skipMacroBlocks(int iPixelWidth, int iPixelHeight) throws MdecException.EndOfStream, MdecException.ReadCorruption { - int iBlockCount = Calc.blocks(iPixelWidth, iPixelHeight); + final public void skipMacroBlocks(int iPixelWidth, int iPixelHeight) throws MdecException.EndOfStream, MdecException.ReadCorruption { + int iBlocksToSkip = Calc.blocks(iPixelWidth, iPixelHeight); MdecCode code = new MdecCode(); - for (int i = 0; i < iBlockCount; i++) { + for (int i = 0; i < iBlocksToSkip; i++) { while (!readMdecCode(code)) { } } } - abstract public void skipPaddingBits() throws MdecException.EndOfStream; - - /** Read the quantization scale and DC coefficient from the bitstream. */ - abstract protected void readQscaleAndDC(@Nonnull MdecCode code) - throws MdecException.ReadCorruption, MdecException.EndOfStream; - - /** Read an AC Coefficient escaped (zero-run, AC level) value. */ - abstract protected void readEscapeAcCode(@Nonnull MdecCode code) - throws MdecException.EndOfStream; - - /** Create an equivalent bitstream compressor. */ - abstract public @Nonnull BitStreamCompressor makeCompressor(); + final public void skipPaddingBits() throws MdecException.EndOfStream { + _endPaddingBits.skipPaddingBits(_bitReader); + } - abstract public @Nonnull Type getType(); + /** Create an equivalent bitstream compressor. + * @throws UnsupportedOperationException if the bitstream cannot be created for this format. */ + abstract public @Nonnull BitStreamCompressor makeCompressor() throws UnsupportedOperationException; @Override - abstract public String toString(); + public String toString() { + return getClass().getSimpleName() + " " + _context.toString() + " offset=" + _bitReader.getWordPosition(); + } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Iki.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Iki.java index 16730f2..8b89ab9 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Iki.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Iki.java @@ -51,9 +51,11 @@ import jpsxdec.psxvideo.encode.MacroBlockEncoder; import jpsxdec.psxvideo.encode.MdecEncoder; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.IO; import jpsxdec.util.IncompatibleException; @@ -153,7 +155,7 @@ public int getFrameBlockCount() { return _iBlockCount; } - public int getBlockQscale(int iBlock) { + public int getBlockQscaleDc(int iBlock) { if (!_blnIsValid) throw new IllegalStateException(); int b1 = _abQscaleDcLookupTable[iBlock] & 0xff; int b2 = _abQscaleDcLookupTable[iBlock+_iBlockCount] & 0xff; @@ -190,37 +192,34 @@ public int getBlockQscale(int iBlock) { @Nonnull private final IkiHeader _header; - private int _iCurrentBlock = 0; - private BitStreamUncompressor_Iki(@Nonnull IkiHeader header, @Nonnull ArrayBitReader bitReader) { - super(BitStreamUncompressor_STRv2.AC_VARIABLE_LENGTH_CODES_MPEG1, bitReader); + super(bitReader, ZeroRunLengthAcLookup_STR.AC_VARIABLE_LENGTH_CODES_MPEG1, + new QuantizationDcReader_Iki(header), BitStreamUncompressor_STRv2.AC_ESCAPE_CODE_STR, + FRAME_END_PADDING_BITS_NONE); _header = header; } - /** Read the quantization scale and DC coefficient from the iki lzss - * compressed header. */ - @Override - protected void readQscaleAndDC(@Nonnull MdecCode code) throws MdecException.EndOfStream { - if (_iCurrentBlock >= _header.getFrameBlockCount()) - throw new MdecException.EndOfStream(MdecException.inBlockOfBlocks(_iCurrentBlock, _header.getFrameBlockCount())); - readBlockQscaleAndDC(code, _iCurrentBlock); - _iCurrentBlock++; - } + private static class QuantizationDcReader_Iki implements IQuantizationDc { - /** Looks up the given block's quantization scale and DC coefficient. */ - private void readBlockQscaleAndDC(@Nonnull MdecCode code, int iBlock) { - code.set(_header.getBlockQscale(iBlock)); - } + @Nonnull + private final BitStreamUncompressor_Iki.IkiHeader _header; - @Override - protected void readEscapeAcCode(MdecCode code) throws MdecException.EndOfStream { - BitStreamUncompressor_STRv2.readStrEscapeAcCode(_bitReader, code, _debug, LOG); - } + public QuantizationDcReader_Iki(@Nonnull BitStreamUncompressor_Iki.IkiHeader header) { + _header = header; + } - @Override - public void skipPaddingBits() { + /** Read the quantization scale and DC coefficient from the iki lzss + * compressed header. */ + /** Looks up the given block's quantization scale and DC coefficient. */ + public void readQuantizationScaleAndDc(@Nonnull ArrayBitReader bitReader, @Nonnull MdecContext context, @Nonnull MdecCode mdecCode) + throws MdecException.ReadCorruption, MdecException.EndOfStream + { + if (context.getTotalBlocksRead() >= _header.getFrameBlockCount()) + throw new MdecException.EndOfStream(MdecException.inBlockOfBlocks(context.getTotalBlocksRead(), _header.getFrameBlockCount())); + mdecCode.set(_header.getBlockQscaleDc(context.getTotalBlocksRead())); + } } /** .iki videos utilize yet another LZSS compression format that is @@ -242,19 +241,19 @@ private static void ikiLzssUncompress(@Nonnull byte[] abSrc, int iSrcPosition, int iFlags = abSrc[iSrcPosition++] & 0xff; - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.err.println("Flags " + Misc.bitsToString(iFlags, 8)); for (int iBit = 0; iBit < 8; iBit++, iFlags >>= 1) { - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.err.format("[InPos: %d OutPos: %d] bit %02x: ", iSrcPosition, iDestPosition, 1 << iBit ); if ((iFlags & 1) == 0) { byte b = abSrc[iSrcPosition++]; - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.err.println(String.format("{Byte %02x}", b)); abDest[iDestPosition++] = b; @@ -267,7 +266,7 @@ private static void ikiLzssUncompress(@Nonnull byte[] abSrc, int iSrcPosition, } iCopyOffset++; - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.err.println( "Copy " + iCopySize + " bytes from " + (iDestPosition - (iCopyOffset + 1)) + "(-"+iCopyOffset+")"); @@ -281,7 +280,7 @@ private static void ikiLzssUncompress(@Nonnull byte[] abSrc, int iSrcPosition, break; } } - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.err.println("Src pos at end: " + iSrcPosition); } @@ -299,7 +298,7 @@ public void compress(@Nonnull byte[] abSrcData, @Nonnull ByteArrayOutputStream o for (int iSrcPos = 0; iSrcPos < abSrcData.length;) { - if (DEBUG) + if (BitStreamDebugging.DEBUG) _logger.format("[InPos: %d OutPos: %d]: bit %02x: ", out.size()+1+_buffer.size(), iSrcPos, 1 << _iFlagBit ); @@ -328,7 +327,7 @@ public void compress(@Nonnull byte[] abSrcData, @Nonnull ByteArrayOutputStream o addRun(iSrcPos - iLongestRunPos, iLongestRunLen, iSrcPos); iSrcPos += iLongestRunLen; } else { - if (DEBUG) _logger.format("{Byte %02x}", abSrcData[iSrcPos]&0xff).println(); + if (BitStreamDebugging.DEBUG) _logger.format("{Byte %02x}", abSrcData[iSrcPos]&0xff).println(); addCopy(abSrcData[iSrcPos]); iSrcPos++; } @@ -336,7 +335,7 @@ public void compress(@Nonnull byte[] abSrcData, @Nonnull ByteArrayOutputStream o } if (_iFlagBit > 0) { - if (DEBUG) _logger.println("Flags " + Misc.bitsToString(_iFlags, 8)); + if (BitStreamDebugging.DEBUG) _logger.println("Flags " + Misc.bitsToString(_iFlags, 8)); out.write(_iFlags); byte[] ab = _buffer.toByteArray(); out.write(ab, 0, ab.length); @@ -346,7 +345,7 @@ public void compress(@Nonnull byte[] abSrcData, @Nonnull ByteArrayOutputStream o private void addRun(int iPosition, int iLength, int iSrcPos) { assert iPosition > 0; - if (DEBUG) _logger.format("Copy %d bytes from %d(%d)", iLength, iSrcPos-iPosition, -iPosition).println(); + if (BitStreamDebugging.DEBUG) _logger.format("Copy %d bytes from %d(%d)", iLength, iSrcPos-iPosition, -iPosition).println(); _iFlags |= (1 << _iFlagBit); _buffer.write(iLength - 3); iPosition--; @@ -365,7 +364,7 @@ private void addCopy(byte b) { private void incFlag(@Nonnull ByteArrayOutputStream out) { _iFlagBit++; if (_iFlagBit >= 8) { - if (DEBUG) { + if (BitStreamDebugging.DEBUG) { System.err.println("Flags " + Misc.bitsToString(_iFlags, 8)); _logger.flush(); System.err.print(_baosLogger.toString()); @@ -399,32 +398,20 @@ private static int matchLength(@Nonnull byte[] abData, int iMatchStart, int iEnd @Override - public @Nonnull Type getType() { - return Type.Iki; - } - public String toString() { - if (_header.isValid()) { - // find the minimum and maximum quantization scales used - int iMinQscale = 64, iMaxQscale = 0; - MdecCode code = new MdecCode(); - for (int i = 0; i < _header.getFrameBlockCount(); i++) { - readBlockQscaleAndDC(code, i); - int iQscale = code.getTop6Bits(); - if (iQscale < iMinQscale) - iMinQscale = iQscale; - if(iQscale > iMaxQscale) - iMaxQscale = iQscale; - } - return String.format("%s Qscale=%d-%d Offset=%d MB=%d.%d Mdec count=%d %dx%d", - getType(), iMinQscale, iMaxQscale, - _bitReader.getWordPosition(), - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - getReadMdecCodeCount(), - _header.getWidth(), _header.getHeight()); - } else { - return "Invalid IKI"; + // find the minimum and maximum quantization scales used + int iMinQscale = 64, iMaxQscale = 0; + MdecCode code = new MdecCode(); + for (int i = 0; i < _header.getFrameBlockCount(); i++) { + code.set(_header.getBlockQscaleDc(i)); + int iQscale = code.getTop6Bits(); + if (iQscale < iMinQscale) + iMinQscale = iQscale; + if(iQscale > iMaxQscale) + iMaxQscale = iQscale; } + return super.toString() + String.format(" Qscale=%d-%d %dx%d", + iMinQscale, iMaxQscale, _header.getWidth(), _header.getHeight()); } @Override @@ -548,7 +535,7 @@ public int compare(MacroBlockEncoder o1, MacroBlockEncoder o2) { if (iNewDemuxSize <= iOriginalLength) { log.log(Level.INFO, I.NEW_FRAME_FITS(sFrameDescription, iNewDemuxSize, iOriginalLength)); } else { - log.log(Level.INFO, I.IKI_NEW_FRAME_GT_SRC_STOPPING(sFrameDescription, iNewDemuxSize, iOriginalLength)); + log.log(Level.INFO, I.NEW_FRAME_DOES_NOT_FIT(sFrameDescription, iNewDemuxSize, iOriginalLength)); break; } abLastGoodDemux = abNewDemux; @@ -590,13 +577,13 @@ public int compare(MacroBlockEncoder o1, MacroBlockEncoder o2) { } @Override - protected void setBlockQscale(int iBlock, int iQscale) { + protected void setBlockQscale(@Nonnull MdecBlock block, int iQscale) { _currentBlockQscaleDc.setTop6Bits(iQscale); } @Override - protected @Nonnull String encodeDC(int iDC, int iBlock) { + protected @Nonnull String encodeDC(int iDC, @Nonnull MdecBlock block) { _currentBlockQscaleDc.setBottom10Bits(iDC); int iMdec = _currentBlockQscaleDc.toMdecWord(); _top8.write(iMdec >> 8); @@ -619,7 +606,7 @@ protected void setBlockQscale(int iBlock, int iQscale) { ab = _bottom8.toByteArray(); byte[] abHdr = new byte[10]; - IO.writeInt16LE(abHdr, 0, (short)calculateHalfCeiling32(iMdecCodeCount)); + IO.writeInt16LE(abHdr, 0, (short)Calc.calculateHalfCeiling32(iMdecCodeCount)); IO.writeInt16LE(abHdr, 2, (short)0x3800); IO.writeInt16LE(abHdr, 4, (short)_iWidth); IO.writeInt16LE(abHdr, 6, (short)_iHeight); diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Lain.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Lain.java index 1f7ba6e..cfe1c71 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Lain.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_Lain.java @@ -46,6 +46,9 @@ import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2.BitStreamCompressor_STRv2; import jpsxdec.psxvideo.encode.MacroBlockEncoder; import jpsxdec.psxvideo.encode.MdecEncoder; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.BinaryDataNotRecognized; @@ -149,11 +152,13 @@ public int getVlcCount() { return new BitStreamUncompressor_Lain(header, bitReader); } - + public static final ZeroRunLengthAc ESCAPE_CODE = new ZeroRunLengthAc(BitStreamCode._000001___________, true, false); + public static final ZeroRunLengthAc END_OF_BLOCK = new ZeroRunLengthAc(BitStreamCode._10_______________, + MdecCode.MDEC_END_OF_DATA_TOP6, MdecCode.MDEC_END_OF_DATA_BOTTOM10, false, true); /** The custom Serial Experiments Lain PlayStation game * AC coefficient variable-length (Huffman) code table. */ - private final static AcLookup AC_VARIABLE_LENGTH_CODES_LAIN = new AcLookup() + private final static ZeroRunLengthAcLookup AC_VARIABLE_LENGTH_CODES_LAIN = new ZeroRunLengthAcLookup.Builder() // Code "Run" "Level" ._11s ( 0 , 1 ) ._011s ( 0 , 2 ) @@ -265,7 +270,10 @@ public int getVlcCount() { ._0000000000011100s ( 0 , 60 ) ._0000000000011101s ( 9 , 2 ) ._0000000000011110s ( 24 , 1 ) - ._0000000000011111s ( 18 , 1 ); + ._0000000000011111s ( 18 , 1 ) + .add(ESCAPE_CODE) + .add(END_OF_BLOCK) + .build(); // ------------------------------------------------------------------------- // ------------------------------------------------------------------------- @@ -275,117 +283,124 @@ public int getVlcCount() { private final LainHeader _header; public BitStreamUncompressor_Lain(@Nonnull LainHeader header, @Nonnull ArrayBitReader bitReader) { - super(AC_VARIABLE_LENGTH_CODES_LAIN, bitReader); + super(bitReader, AC_VARIABLE_LENGTH_CODES_LAIN, + new QuantizationDcReader_Lain(header.getLumaQscale(), header.getChromaQscale()), + AC_ESCAPE_CODE_LAIN, FRAME_END_PADDING_BITS_NONE); _header = header; } - @Override - protected void readQscaleAndDC(@Nonnull MdecCode code) throws MdecException.EndOfStream { - code.setBottom10Bits( _bitReader.readSignedBits(10) ); - assert !DEBUG || _debug.append(Misc.bitsToString(code.getBottom10Bits(), 10)); - if (getCurrentMacroBlockSubBlock() < 2) - code.setTop6Bits(_header.getChromaQscale()); - else - code.setTop6Bits(_header.getLumaQscale()); - } + private static class QuantizationDcReader_Lain implements IQuantizationDc { + private final int _iFrameLumaQuantizationScale; + private final int _iFrameChromaQuantizationScale; - - protected void readEscapeAcCode(@Nonnull MdecCode code) throws MdecException.EndOfStream - { + public QuantizationDcReader_Lain(int iFrameLumaQuantizationScale, + int iFrameChromaQuantizationScale) + { + _iFrameLumaQuantizationScale = iFrameLumaQuantizationScale; + _iFrameChromaQuantizationScale = iFrameChromaQuantizationScale; + } - int iBits = _bitReader.readUnsignedBits(6+8); + public void readQuantizationScaleAndDc(@Nonnull ArrayBitReader bitReader, + @Nonnull MdecContext context, + @Nonnull MdecCode code) + throws MdecException.EndOfStream + { + code.setBottom10Bits(bitReader.readSignedBits(10) ); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(code.getBottom10Bits(), 10)); + if (context.getCurrentBlock().isChroma()) + code.setTop6Bits(_iFrameChromaQuantizationScale); + else + code.setTop6Bits(_iFrameLumaQuantizationScale); + } + } - // Get the (6 bit) run of zeros from the bits already read - // 17 bits: eeeeeezzzzzz_____ : e = escape code, z = run of zeros - code.setTop6Bits( (iBits >>> 8) & 63 ); - assert !DEBUG || _debug.append(Misc.bitsToString(code.getTop6Bits(), 6)); + private static class AcEscapeCode_Lain implements IAcEscapeCode { - // Lain - - /* Lain playstation uses mpeg1 specification escape code - Fixed Length Code Level - forbidden -256 - 1000 0000 0000 0001 -255 - 1000 0000 0000 0010 -254 - ... - 1000 0000 0111 1111 -129 - 1000 0000 1000 0000 -128 - 1000 0001 -127 - 1000 0010 -126 - ... - 1111 1110 -2 - 1111 1111 -1 - forbidden 0 - 0000 0001 1 - 0000 0010 2 - ... - 0111 1110 126 - 0111 1111 127 - 0000 0000 1000 0000 128 - 0000 0000 1000 0001 129 - ... - 0000 0000 1111 1110 254 - 0000 0000 1111 1111 255 - */ - // Chop down to the first 8 bits - iBits = iBits & 0xff; - int iACCoefficient; - if (iBits == 0x00) { - // If it's the special 00000000 - // Positive - assert !DEBUG || _debug.append("00000000"); - - iACCoefficient = _bitReader.readUnsignedBits(8); - - assert !DEBUG || _debug.append(Misc.bitsToString(iACCoefficient, 8)); - - code.setBottom10Bits(iACCoefficient); - } else if (iBits == 0x80) { - // If it's the special 10000000 - // Negative - assert !DEBUG || _debug.append("10000000"); - - iACCoefficient = -256 + _bitReader.readUnsignedBits(8); - - assert !DEBUG || _debug.append(Misc.bitsToString(iACCoefficient, 8)); - - code.setBottom10Bits(iACCoefficient); - } else { - // Otherwise we already have the value - assert !DEBUG || _debug.append(Misc.bitsToString(iBits, 8)); - - // changed to signed - iACCoefficient = (byte)iBits; - - code.setBottom10Bits(iACCoefficient); - } + public void readAcEscapeCode(@Nonnull ArrayBitReader bitReader, @Nonnull MdecCode code) throws MdecException.EndOfStream + { - } + int iBits = bitReader.readUnsignedBits(6+8); + + // Get the (6 bit) run of zeros from the bits already read + // 17 bits: eeeeeezzzzzz_____ : e = escape code, z = run of zeros + code.setTop6Bits( (iBits >>> 8) & 63 ); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(code.getTop6Bits(), 6)); + + // Lain + + /* Lain playstation uses mpeg1 specification escape code + Fixed Length Code Level + forbidden -256 + 1000 0000 0000 0001 -255 + 1000 0000 0000 0010 -254 + ... + 1000 0000 0111 1111 -129 + 1000 0000 1000 0000 -128 + 1000 0001 -127 + 1000 0010 -126 + ... + 1111 1110 -2 + 1111 1111 -1 + forbidden 0 + 0000 0001 1 + 0000 0010 2 + ... + 0111 1110 126 + 0111 1111 127 + 0000 0000 1000 0000 128 + 0000 0000 1000 0001 129 + ... + 0000 0000 1111 1110 254 + 0000 0000 1111 1111 255 + */ + // Chop down to the first 8 bits + iBits = iBits & 0xff; + int iACCoefficient; + if (iBits == 0x00) { + // If it's the special 00000000 + // Positive + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits("00000000"); + + iACCoefficient = bitReader.readUnsignedBits(8); + + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(iACCoefficient, 8)); + + code.setBottom10Bits(iACCoefficient); + } else if (iBits == 0x80) { + // If it's the special 10000000 + // Negative + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits("10000000"); + + iACCoefficient = -256 + bitReader.readUnsignedBits(8); + + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(iACCoefficient, 8)); + + code.setBottom10Bits(iACCoefficient); + } else { + // Otherwise we already have the value + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(iBits, 8)); - @Override - public void skipPaddingBits() { - // Lain doesn't have ending padding bits - } + // changed to signed + iACCoefficient = (byte)iBits; - @Override - public @Nonnull Type getType() { - return Type.Lain; + code.setBottom10Bits(iACCoefficient); + } + + } } + private static final AcEscapeCode_Lain AC_ESCAPE_CODE_LAIN = new AcEscapeCode_Lain(); + @Override public String toString() { - return String.format("%s Qscale L=%d C=%d 3800=%x Offset=%d MB=%d.%d Mdec count=%d", - getType(), _header.getLumaQscale(), _header.getChromaQscale(), - _header.getMagic3800orFrame(), - _bitReader.getWordPosition(), - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - getReadMdecCodeCount()); + return super.toString() + " Qscale L=" + _header.getLumaQscale() + +" C=" + _header.getChromaQscale(); } @Override public @Nonnull BitStreamCompressor_Lain makeCompressor() { - return new BitStreamCompressor_Lain(getFullMacroBlocksRead(), _header.getMagic3800orFrame()); + return new BitStreamCompressor_Lain(_context.getTotalMacroBlocksRead(), _header.getMagic3800orFrame()); } // ========================================================================= @@ -458,7 +473,7 @@ public BitStreamCompressor_Lain(int iMacroBlockCount, int iMagic3800orFrame) { { LainHeader header = new LainHeader(abOriginal, abOriginal.length); if (!header.isValid()) - throw new LocalizedIncompatibleException(I.FRAME_NOT_LAIN()); + throw new LocalizedIncompatibleException(I.FRAME_IS_NOT_BITSTREAM_FORMAT("Lain")); final int iFrameLQscale = header.getLumaQscale(); final int iFrameCQscale = header.getChromaQscale(); @@ -515,8 +530,8 @@ public BitStreamCompressor_Lain(int iMacroBlockCount, int iMagic3800orFrame) { } @Override - protected void setBlockQscale(int iBlock, int iQscale) throws IncompatibleException { - if (iBlock < 2) { + protected void setBlockQscale(@Nonnull MdecBlock block, int iQscale) throws IncompatibleException { + if (block.isChroma()) { if (_iChromaQscale < 0) _iChromaQscale = iQscale; else if (_iChromaQscale != iQscale) { @@ -560,7 +575,7 @@ protected int getHeaderVersion() { } @Override - protected @Nonnull AcLookup getAcVaribleLengthCodeList() { + protected @Nonnull ZeroRunLengthAcLookup getAcVaribleLengthCodeList() { return AC_VARIABLE_LENGTH_CODES_LAIN; } @@ -580,12 +595,12 @@ protected int getHeaderVersion() { "Unable to escape %s, AC code too large for Lain", code)); if (code.getBottom10Bits() >= -127 && code.getBottom10Bits() <= 127) { - return AcLookup.ESCAPE_CODE.BitString + sTopBits + Misc.bitsToString(code.getBottom10Bits(), 8); + return ESCAPE_CODE.getBitString() + sTopBits + Misc.bitsToString(code.getBottom10Bits(), 8); } else { if (code.getBottom10Bits() > 0) { - return AcLookup.ESCAPE_CODE.BitString + sTopBits + "00000000" + Misc.bitsToString(code.getBottom10Bits(), 8); + return ESCAPE_CODE.getBitString() + sTopBits + "00000000" + Misc.bitsToString(code.getBottom10Bits(), 8); } else { - return AcLookup.ESCAPE_CODE.BitString + sTopBits + "10000000" + Misc.bitsToString(code.getBottom10Bits()+256, 8); + return ESCAPE_CODE.getBitString() + sTopBits + "10000000" + Misc.bitsToString(code.getBottom10Bits()+256, 8); } } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv1.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv1.java index 2babfbf..f444b63 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv1.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv1.java @@ -51,18 +51,24 @@ * This case obviously works with PlayStation hardware, so is already * accepted by the STRv2 uncompressor to make things simpler, faster, and * more robust (because AC=0 may occur in games besides FF7). See - * {@link BitStreamUncompressor_STRv2#readEscapeAcCode(jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode)}. + * {@link BitStreamUncompressor_STRv2#readEscapeAcCode(jpsxdec.psxvideo.mdec.MdecCode)}. *

        * I suspect the reason for this FF7 AC=0 waste is because they compressed the * frames further to fit camera data. This led to AC values being reduced, * some falling to 0, but they didn't merge those codes to save space. */ -public class BitStreamUncompressor_STRv1 extends BitStreamUncompressor_STRv2 { +public class BitStreamUncompressor_STRv1 extends BitStreamUncompressor { public static class StrV1Header extends StrHeader { public StrV1Header(byte[] abFrameData, int iDataSize) { super(abFrameData, iDataSize, 1); } + + public @Nonnull BitStreamUncompressor_STRv1 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) + throws BinaryDataNotRecognized + { + return BitStreamUncompressor_STRv1.makeV1(abBitstream, iBitstreamSize); + } } public static @Nonnull BitStreamUncompressor_STRv1 makeV1(@Nonnull byte[] abFrameData) @@ -84,28 +90,30 @@ public StrV1Header(byte[] abFrameData, int iDataSize) { StrV1Header header = new StrV1Header(abFrameData, iDataSize); if (!header.isValid()) return null; - ArrayBitReader bitReader = makeStrBitReader(abFrameData, iDataSize); + ArrayBitReader bitReader = BitStreamUncompressor_STRv2.makeStrBitReader(abFrameData, iDataSize); return new BitStreamUncompressor_STRv1(header, bitReader); } - public BitStreamUncompressor_STRv1(@Nonnull StrHeader header, + @Nonnull + private final StrV1Header _header; + + public BitStreamUncompressor_STRv1(@Nonnull StrV1Header header, @Nonnull ArrayBitReader bitReader) { - super(header, bitReader); + super(bitReader, ZeroRunLengthAcLookup_STR.AC_VARIABLE_LENGTH_CODES_MPEG1, + new BitStreamUncompressor_STRv2.QuantizationDc_STRv12(header.getQuantizationScale()), + BitStreamUncompressor_STRv2.AC_ESCAPE_CODE_STR, + BitStreamUncompressor_STRv2.FRAME_END_PADDING_BITS_STRV2); + _header = header; } - @Override - public @Nonnull Type getType() { - return Type.STRv1; - } - @Override public @Nonnull BitStreamCompressor_STRv1 makeCompressor() { - return new BitStreamCompressor_STRv1(getFullMacroBlocksRead()); + return new BitStreamCompressor_STRv1(_context.getTotalMacroBlocksRead()); } - public static class BitStreamCompressor_STRv1 extends BitStreamCompressor_STRv2 { + public static class BitStreamCompressor_STRv1 extends BitStreamUncompressor_STRv2.BitStreamCompressor_STRv2 { private BitStreamCompressor_STRv1(int iMacroBlockCount) { super(iMacroBlockCount); @@ -118,8 +126,8 @@ private BitStreamCompressor_STRv1(int iMacroBlockCount) { protected int getFrameQscale(@Nonnull byte[] abFrameData) throws LocalizedIncompatibleException { StrV1Header header = new StrV1Header(abFrameData, abFrameData.length); if (!header.isValid()) - throw new LocalizedIncompatibleException(I.FRAME_NOT_STRV1()); - return header.getQscale(); + throw new LocalizedIncompatibleException(I.FRAME_IS_NOT_BITSTREAM_FORMAT("STRv1")); + return header.getQuantizationScale(); } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv2.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv2.java index 56ab56d..5af6c0c 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv2.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv2.java @@ -44,14 +44,14 @@ import jpsxdec.i18n.I; import jpsxdec.i18n.exception.LocalizedIncompatibleException; import jpsxdec.i18n.log.ILocalizedLogger; -import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor.AcBitCode; -import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor.AcLookup; -import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor.MdecDebugger; import jpsxdec.psxvideo.encode.MacroBlockEncoder; import jpsxdec.psxvideo.encode.MdecEncoder; +import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.IO; import jpsxdec.util.IncompatibleException; @@ -68,6 +68,12 @@ public static class StrV2Header extends StrHeader { public StrV2Header(byte[] abFrameData, int iDataSize) { super(abFrameData, iDataSize, 2); } + + public @Nonnull BitStreamUncompressor_STRv2 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) + throws BinaryDataNotRecognized + { + return BitStreamUncompressor_STRv2.makeV2(abBitstream, iBitstreamSize); + } } public static @Nonnull BitStreamUncompressor_STRv2 makeV2(@Nonnull byte[] abBitstream) @@ -94,125 +100,6 @@ public StrV2Header(byte[] abFrameData, int iDataSize) { return new BitStreamUncompressor_STRv2(header, bitReader); } - /** STR v2, v3, FF7, and .iki AC coefficient variable-length (Huffman) codes. - * Conveniently identical to MPEG1. */ - static final AcLookup AC_VARIABLE_LENGTH_CODES_MPEG1 = new AcLookup() - // Code "Run" "Level" - // Table 1 - ._11s (0 , 1) - ._011s (1 , 1) - ._0100s (0 , 2) - ._0101s (2 , 1) - ._00101s (0 , 3) - ._00110s (4 , 1) - ._00111s (3 , 1) - ._000100s (7 , 1) - ._000101s (6 , 1) - ._000110s (1 , 2) - ._000111s (5 , 1) - ._0000100s (2 , 2) - ._0000101s (9 , 1) - ._0000110s (0 , 4) - ._0000111s (8 , 1) - ._00100000s (13, 1) - ._00100001s (0 , 6) - ._00100010s (12, 1) - ._00100011s (11, 1) - ._00100100s (3 , 2) - ._00100101s (1 , 3) - ._00100110s (0 , 5) - ._00100111s (10, 1) - // Table 2 - ._0000001000s (16, 1) - ._0000001001s (5 , 2) - ._0000001010s (0 , 7) - ._0000001011s (2 , 3) - ._0000001100s (1 , 4) - ._0000001101s (15, 1) - ._0000001110s (14, 1) - ._0000001111s (4 , 2) - ._000000010000s (0 , 11) - ._000000010001s (8 , 2) - ._000000010010s (4 , 3) - ._000000010011s (0 , 10) - ._000000010100s (2 , 4) - ._000000010101s (7 , 2) - ._000000010110s (21, 1) - ._000000010111s (20, 1) - ._000000011000s (0 , 9) - ._000000011001s (19, 1) - ._000000011010s (18, 1) - ._000000011011s (1 , 5) - ._000000011100s (3 , 3) - ._000000011101s (0 , 8) - ._000000011110s (6 , 2) - ._000000011111s (17, 1) - ._0000000010000s (10, 2) - ._0000000010001s (9 , 2) - ._0000000010010s (5 , 3) - ._0000000010011s (3 , 4) - ._0000000010100s (2 , 5) - ._0000000010101s (1 , 7) - ._0000000010110s (1 , 6) - ._0000000010111s (0 , 15) - ._0000000011000s (0 , 14) - ._0000000011001s (0 , 13) - ._0000000011010s (0 , 12) - ._0000000011011s (26, 1) - ._0000000011100s (25, 1) - ._0000000011101s (24, 1) - ._0000000011110s (23, 1) - ._0000000011111s (22, 1) - // Table 3 - ._00000000010000s (0 , 31) - ._00000000010001s (0 , 30) - ._00000000010010s (0 , 29) - ._00000000010011s (0 , 28) - ._00000000010100s (0 , 27) - ._00000000010101s (0 , 26) - ._00000000010110s (0 , 25) - ._00000000010111s (0 , 24) - ._00000000011000s (0 , 23) - ._00000000011001s (0 , 22) - ._00000000011010s (0 , 21) - ._00000000011011s (0 , 20) - ._00000000011100s (0 , 19) - ._00000000011101s (0 , 18) - ._00000000011110s (0 , 17) - ._00000000011111s (0 , 16) - ._000000000010000s (0 , 40) - ._000000000010001s (0 , 39) - ._000000000010010s (0 , 38) - ._000000000010011s (0 , 37) - ._000000000010100s (0 , 36) - ._000000000010101s (0 , 35) - ._000000000010110s (0 , 34) - ._000000000010111s (0 , 33) - ._000000000011000s (0 , 32) - ._000000000011001s (1 , 14) - ._000000000011010s (1 , 13) - ._000000000011011s (1 , 12) - ._000000000011100s (1 , 11) - ._000000000011101s (1 , 10) - ._000000000011110s (1 , 9) - ._000000000011111s (1 , 8) - ._0000000000010000s(1 , 18) - ._0000000000010001s(1 , 17) - ._0000000000010010s(1 , 16) - ._0000000000010011s(1 , 15) - ._0000000000010100s(6 , 3) - ._0000000000010101s(16, 2) - ._0000000000010110s(15, 2) - ._0000000000010111s(14, 2) - ._0000000000011000s(13, 2) - ._0000000000011001s(12, 2) - ._0000000000011010s(11, 2) - ._0000000000011011s(31, 1) - ._0000000000011100s(30, 1) - ._0000000000011101s(29, 1) - ._0000000000011110s(28, 1) - ._0000000000011111s(27, 1); - /** 11 bits found at the end of STR v2 movies. *

        011 111 111 10
        */ private final static String END_OF_FRAME_EXTRA_BITS = "01111111110"; @@ -221,125 +108,72 @@ public StrV2Header(byte[] abFrameData, int iDataSize) { *
        011 111 111 10
        */ private static final int b01111111110 = 0x3FE; - // ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- - - protected abstract static class StrHeader { - /** Frame's quantization scale. */ - private int _iQscale = -1; - private int _iHalfVlcCountCeil32 = -1; - - private final boolean _blnIsValid; - protected StrHeader(@Nonnull byte[] abFrameData, int iDataSize, - int iExpectedVersion) - { - if (iDataSize < 8) { - _blnIsValid = false; - } else { - int iHalfVlcCountCeil32 = IO.readSInt16LE(abFrameData, 0); - int iMagic3800 = IO.readUInt16LE(abFrameData, 2); - int iQscale = IO.readSInt16LE(abFrameData, 4); - int iVersion = IO.readSInt16LE(abFrameData, 6); - - _blnIsValid = iMagic3800 == 0x3800 && - iQscale >= 1 && - iVersion == iExpectedVersion && - iHalfVlcCountCeil32 >= 0; - if (_blnIsValid) { - _iQscale = iQscale; - _iHalfVlcCountCeil32 = iHalfVlcCountCeil32; - } - } - } - - public int getQscale() { - if (!_blnIsValid) throw new IllegalStateException(); - return _iQscale; - } - - public int getHalfVlcCountCeil32() { - if (!_blnIsValid) throw new IllegalStateException(); - return _iHalfVlcCountCeil32; - } - - public boolean isValid() { - return _blnIsValid; - } - - } - protected static @Nonnull ArrayBitReader makeStrBitReader(@Nonnull byte[] abBitstream, int iDataSize) { + public static @Nonnull ArrayBitReader makeStrBitReader(@Nonnull byte[] abBitstream, int iDataSize) { return new ArrayBitReader(abBitstream, iDataSize, true, 8); } - protected final StrHeader _header; + @Nonnull + private final StrV2Header _header; - protected BitStreamUncompressor_STRv2(@Nonnull StrHeader header, - @Nonnull ArrayBitReader bitReader) + private BitStreamUncompressor_STRv2(@Nonnull StrV2Header header, + @Nonnull ArrayBitReader bitReader) { - super(AC_VARIABLE_LENGTH_CODES_MPEG1, bitReader); + super(bitReader, ZeroRunLengthAcLookup_STR.AC_VARIABLE_LENGTH_CODES_MPEG1, + new QuantizationDc_STRv12(header.getQuantizationScale()), new AcEscapeCode_STR(), + new FrameEndPaddingBits_STRv2()); _header = header; } - protected void readQscaleAndDC(@Nonnull MdecCode code) - throws MdecException.ReadCorruption, MdecException.EndOfStream - { - code.setTop6Bits(_header.getQscale()); - code.setBottom10Bits(_bitReader.readSignedBits(10)); - assert !DEBUG || _debug.append(Misc.bitsToString(code.getBottom10Bits(), 10)); - assert !code.isEOD(); // a Qscale of 63 and DC of -512 would look like EOD - } + public static class QuantizationDc_STRv12 implements IQuantizationDc { - protected void readEscapeAcCode(@Nonnull MdecCode code) - throws MdecException.EndOfStream - { - readStrEscapeAcCode(_bitReader, code, _debug, LOG); - } + private final int _iFrameQuantizationScale; - static void readStrEscapeAcCode(@Nonnull ArrayBitReader bitReader, - @Nonnull MdecCode code, - @CheckForNull MdecDebugger debug, - @Nonnull Logger log) - throws MdecException.EndOfStream - { - // Normal playstation encoding stores the escape code in 16 bits: - // 6 for run of zeros, 10 for AC Coefficient - int iRunAndAc = bitReader.readUnsignedBits(6 + 10); - code.set(iRunAndAc); - assert !DEBUG || debug.append(Misc.bitsToString(iRunAndAc, 16)); - - // Ignore AC == 0 coefficients. - // (I consider this an error, but FF7 and other games have these codes, - // so clearly the MDEC can handle it.) - if (code.getBottom10Bits() == 0) { - log.info("Escape code has 0 AC coefficient."); + public QuantizationDc_STRv12(int iFrameQuantizationScale) { + _iFrameQuantizationScale = iFrameQuantizationScale; } - } - @Override - public void skipPaddingBits() throws MdecException.EndOfStream { - int iPaddingBits = _bitReader.readUnsignedBits(11); - if (iPaddingBits != b01111111110) - LOG.log(Level.WARNING, "Incorrect padding bits {0}", Misc.bitsToString(iPaddingBits, 11)); + public void readQuantizationScaleAndDc(@Nonnull ArrayBitReader bitReader, @Nonnull MdecContext context, @Nonnull MdecCode code) + throws MdecException.ReadCorruption, MdecException.EndOfStream + { + code.setTop6Bits(_iFrameQuantizationScale); + code.setBottom10Bits(bitReader.readSignedBits(10)); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(code.getBottom10Bits(), 10)); + assert !code.isEOD(); // a Qscale of 63 and DC of -512 would look like EOD + } } - @Override - public @Nonnull BitStreamCompressor_STRv2 makeCompressor() { - return new BitStreamCompressor_STRv2(getFullMacroBlocksRead()); + private static class AcEscapeCode_STR implements IAcEscapeCode { + public void readAcEscapeCode(@Nonnull ArrayBitReader bitReader, @Nonnull MdecCode code) + throws MdecException.EndOfStream + { + // Normal playstation encoding stores the escape code in 16 bits: + // 6 for run of zeros, 10 for AC Coefficient + int iRunAndAc = bitReader.readUnsignedBits(6 + 10); + code.set(iRunAndAc); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(iRunAndAc, 16)); + } + } + public static final IAcEscapeCode AC_ESCAPE_CODE_STR = new AcEscapeCode_STR(); + + private static class FrameEndPaddingBits_STRv2 implements IFrameEndPaddingBits { + @Override + public void skipPaddingBits(@Nonnull ArrayBitReader bitReader) throws MdecException.EndOfStream { + int iPaddingBits = bitReader.readUnsignedBits(11); + if (iPaddingBits != b01111111110) + LOG.log(Level.WARNING, "Incorrect padding bits {0}", Misc.bitsToString(iPaddingBits, 11)); + } } + public static final IFrameEndPaddingBits FRAME_END_PADDING_BITS_STRV2 = new FrameEndPaddingBits_STRv2(); @Override - public @Nonnull Type getType() { - return Type.STRv2; + public String toString() { + return super.toString() + " Qscale=" + _header.getQuantizationScale(); } - public String toString() { - return String.format("%s Qscale=%d, Current Offset=%d, Current MB.Blk=%d.%d, MDEC count=%d", - getType(), _header.getQscale(), - _bitReader.getWordPosition(), - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - getReadMdecCodeCount()); + @Override + public @Nonnull BitStreamCompressor_STRv2 makeCompressor() { + return new BitStreamCompressor_STRv2(_context.getTotalMacroBlocksRead()); } /*########################################################################*/ @@ -354,7 +188,7 @@ public static class BitStreamCompressor_STRv2 implements BitStreamCompressor { private int _iQscale; private int _iMdecCodeCount; - protected BitStreamCompressor_STRv2(int iMacroBlockCount) { + public BitStreamCompressor_STRv2(int iMacroBlockCount) { _iMacroBlockCount = iMacroBlockCount; } @@ -441,47 +275,41 @@ protected BitStreamCompressor_STRv2(int iMacroBlockCount) { bitStream.setLittleEndian(isBitstreamLittleEndian()); final MdecCode code = new MdecCode(); - int iMdecCodeCount = 0; + MdecContext context = new MdecContext(); - boolean blnNewBlk = true; - int iBlock = 0; - for (int iMacroBlock = 0; iMacroBlock < _iMacroBlockCount;) { + while (context.getTotalMacroBlocksRead() < _iMacroBlockCount) { String sBitsToWrite; boolean blnEod = inStream.readMdecCode(code); if (!code.isValid()) throw new MdecException.ReadCorruption("Invalid MDEC code " + code); if (blnEod) { - sBitsToWrite = AcLookup.END_OF_BLOCK.BitString; - blnNewBlk = true; - iBlock = (iBlock + 1) % 6; - if (iBlock == 0) - iMacroBlock++; + sBitsToWrite = ZeroRunLengthAcLookup_STR.END_OF_BLOCK.getBitString(); + context.nextCodeEndBlock(); } else { - if (blnNewBlk) { - setBlockQscale(iBlock, code.getTop6Bits()); - sBitsToWrite = encodeDC(code.getBottom10Bits(), iBlock); - blnNewBlk = false; + if (context.atStartOfBlock()) { + setBlockQscale(context.getCurrentBlock(), code.getTop6Bits()); + sBitsToWrite = encodeDC(code.getBottom10Bits(), context.getCurrentBlock()); } else { sBitsToWrite = encodeAC(code); } + context.nextCode(); } - iMdecCodeCount++; - if (DEBUG) + if (BitStreamDebugging.DEBUG) System.out.println("Converting " + code.toString() + " to " + sBitsToWrite + " at " + bitStream.getCurrentWordPosition()); bitStream.write(sBitsToWrite); } - if (iBlock != 0) + if (!context.atStartOfBlock()) throw new IllegalStateException("Ended compressing in the middle of a macroblock."); addTrailingBits(bitStream); byte[] abBitstream = bitStream.toByteArray(); - byte[] abHeader = createHeader(iMdecCodeCount); + byte[] abHeader = createHeader(context.getTotalMdecCodesRead()); byte[] abReturn = new byte[abHeader.length + abBitstream.length]; System.arraycopy(abHeader, 0, abReturn, 0, abHeader.length); System.arraycopy(abBitstream, 0, abReturn, abHeader.length, abBitstream.length); - _iMdecCodeCount = iMdecCodeCount; + _iMdecCodeCount = context.getTotalMdecCodesRead(); return abReturn; } @@ -501,7 +329,7 @@ protected void addTrailingBits(@Nonnull BitStreamWriter bitStream) { * Performs any necessary preparations for encoding the block. * Ensures the quantization scale is compatible with the bitstream. * Caller will ensure parameters are valid. */ - protected void setBlockQscale(int iBlock, int iQscale) throws IncompatibleException { + protected void setBlockQscale(@Nonnull MdecBlock block, int iQscale) throws IncompatibleException { if (_iQscale < 0) _iQscale = iQscale; else if (_iQscale != iQscale) @@ -510,11 +338,9 @@ else if (_iQscale != iQscale) _iQscale, iQscale)); } - protected @Nonnull String encodeDC(int iDC, int iBlock) throws MdecException.TooMuchEnergy { + protected @Nonnull String encodeDC(int iDC, @Nonnull MdecBlock block) throws MdecException.TooMuchEnergy { if (iDC < -512 || iDC > 511) throw new IllegalArgumentException("Invalid DC code " + iDC); - if (iBlock < 0 || iBlock > 5) - throw new IllegalArgumentException("Invalid block " + iBlock); return Misc.bitsToString(iDC, 10); } @@ -525,17 +351,16 @@ private String encodeAC(@Nonnull MdecCode code) if (!code.isValid()) throw new IllegalArgumentException("Invalid MDEC code " + code); - for (AcBitCode vlc : getAcVaribleLengthCodeList().getCodeList()) { - if (code.getTop6Bits() == vlc.ZeroRun && Math.abs(code.getBottom10Bits()) == vlc.AcCoefficient) { - return vlc.BitString.replace('s', (code.getBottom10Bits() < 0) ? '1' : '0'); - } + for (ZeroRunLengthAc vlc : getAcVaribleLengthCodeList()) { + if (vlc.equalsMdec(code)) + return vlc.getBitString(); } // not a pre-defined code return encodeAcEscape(code); } - protected @Nonnull AcLookup getAcVaribleLengthCodeList() { - return AC_VARIABLE_LENGTH_CODES_MPEG1; + protected @Nonnull ZeroRunLengthAcLookup getAcVaribleLengthCodeList() { + return ZeroRunLengthAcLookup_STR.AC_VARIABLE_LENGTH_CODES_MPEG1; } protected @Nonnull String encodeAcEscape(@Nonnull MdecCode code) @@ -544,7 +369,7 @@ private String encodeAC(@Nonnull MdecCode code) if (!code.isValid()) throw new IllegalArgumentException("Invalid MDEC code " + code); - return AcLookup.ESCAPE_CODE.BitString + + return ZeroRunLengthAcLookup_STR.ESCAPE_CODE.getBitString() + Misc.bitsToString(code.getTop6Bits(), 6) + Misc.bitsToString(code.getBottom10Bits(), 10); } @@ -552,7 +377,7 @@ private String encodeAC(@Nonnull MdecCode code) protected @Nonnull byte[] createHeader(int iMdecCodeCount) { byte[] ab = new byte[8]; - IO.writeInt16LE(ab, 0, calculateHalfCeiling32(iMdecCodeCount)); + IO.writeInt16LE(ab, 0, Calc.calculateHalfCeiling32(iMdecCodeCount)); IO.writeInt16LE(ab, 2, (short)0x3800); IO.writeInt16LE(ab, 4, (short)_iQscale); IO.writeInt16LE(ab, 6, (short)getHeaderVersion()); @@ -565,14 +390,9 @@ private String encodeAC(@Nonnull MdecCode code) protected int getFrameQscale(@Nonnull byte[] abFrameData) throws LocalizedIncompatibleException { StrV2Header header = new StrV2Header(abFrameData, abFrameData.length); if (!header.isValid()) - throw new LocalizedIncompatibleException(I.FRAME_NOT_STRV2()); - return header.getQscale(); + throw new LocalizedIncompatibleException(I.FRAME_IS_NOT_BITSTREAM_FORMAT("STRv2")); + return header.getQuantizationScale(); } } - - /** Debug */ - public static void main(String[] args) { - AC_VARIABLE_LENGTH_CODES_MPEG1.print(System.out); - } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv3.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv3.java index 9094787..e640b46 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv3.java +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/BitStreamUncompressor_STRv3.java @@ -43,23 +43,31 @@ import javax.annotation.Nonnull; import jpsxdec.i18n.I; import jpsxdec.i18n.exception.LocalizedIncompatibleException; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.util.BinaryDataNotRecognized; import jpsxdec.util.IncompatibleException; import jpsxdec.util.Misc; /** Uncompressor for demuxed STR v3 video bitstream data. * Makes use of most of STR v2 code. Adds v3 handling for DC values. */ -public class BitStreamUncompressor_STRv3 extends BitStreamUncompressor_STRv2 { +public class BitStreamUncompressor_STRv3 extends BitStreamUncompressor { private static final Logger LOG = Logger.getLogger(BitStreamUncompressor_STRv3.class.getName()); public static class StrV3Header extends StrHeader { - public StrV3Header(byte[] abFrameData, int iDataSize) { + public StrV3Header(@Nonnull byte[] abFrameData, int iDataSize) { super(abFrameData, iDataSize, 3); } + + public @Nonnull BitStreamUncompressor_STRv3 makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) + throws BinaryDataNotRecognized + { + return BitStreamUncompressor_STRv3.makeV3(abBitstream, iBitstreamSize); + } } public static @Nonnull BitStreamUncompressor_STRv3 makeV3(@Nonnull byte[] abFrameData) @@ -81,7 +89,7 @@ public StrV3Header(byte[] abFrameData, int iDataSize) { StrV3Header header = new StrV3Header(abFrameData, iDataSize); if (!header.isValid()) return null; - ArrayBitReader bitReader = makeStrBitReader(abFrameData, iDataSize); + ArrayBitReader bitReader = BitStreamUncompressor_STRv2.makeStrBitReader(abFrameData, iDataSize); return new BitStreamUncompressor_STRv3(header, bitReader); } @@ -116,8 +124,7 @@ public void fillLookupArray(@Nonnull DcVariableLengthCode[] aoLookup, int iLonge } } - abstract public int readDc(@Nonnull ArrayBitReader bitReader, - @CheckForNull MdecDebugger debug) + abstract public int readDc(@Nonnull ArrayBitReader bitReader) throws MdecException.EndOfStream; /** Attempts to encode a DC value that has already been diff'ed from * the previous value and divided by 4. @@ -137,7 +144,7 @@ public DcVlc0(@Nonnull String sCode, int iDifferentialBitLen, int iDifferential) } @Override - public int readDc(@Nonnull ArrayBitReader bitReader, @CheckForNull MdecDebugger debug) { + public int readDc(@Nonnull ArrayBitReader bitReader) { return _iDifferential; } @@ -173,11 +180,11 @@ public DcVlc_(@Nonnull String sCode, int iDifferentialBitLen, } @Override - public int readDc(@Nonnull ArrayBitReader bitReader, @CheckForNull MdecDebugger debug) + public int readDc(@Nonnull ArrayBitReader bitReader) throws MdecException.EndOfStream { int iDC_Differential = bitReader.readUnsignedBits(_iDifferentialBitLen); - assert !DEBUG || debug.append(Misc.bitsToString(iDC_Differential, _iDifferentialBitLen)); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(Misc.bitsToString(iDC_Differential, _iDifferentialBitLen)); // top bit == 0 means it's negative if ((iDC_Differential & _iTopBitMask) == 0) { iDC_Differential += _iNegativeDifferentialMin; @@ -280,116 +287,131 @@ public int readDc(@Nonnull ArrayBitReader bitReader, @CheckForNull MdecDebugger // ## Instance stuff ###################################################### // ######################################################################## - /** Holds the previous DC values during a version 3 frame decoding. */ - private int _iPreviousCr_DC = 0, - _iPreviousCb_DC = 0, - _iPreviousY_DC = 0; + @Nonnull + private final StrV3Header _header; public BitStreamUncompressor_STRv3(@Nonnull StrV3Header header, @Nonnull ArrayBitReader bitReader) { - super(header, bitReader); + super(bitReader, ZeroRunLengthAcLookup_STR.AC_VARIABLE_LENGTH_CODES_MPEG1, + new QuantizationDcReader_STRv3(header.getQuantizationScale()), + BitStreamUncompressor_STRv2.AC_ESCAPE_CODE_STR, + FRAME_END_PADDING_BITS_STRV3); + _header = header; } - @Override - protected void readQscaleAndDC(@Nonnull MdecCode code) - throws MdecException.ReadCorruption, MdecException.EndOfStream - { - code.setTop6Bits(_header.getQscale()); - switch (getCurrentMacroBlockSubBlock()) { - case 0: - code.setBottom10Bits(_iPreviousCr_DC = readV3DcChroma(_iPreviousCr_DC)); - return; - case 1: - code.setBottom10Bits(_iPreviousCb_DC = readV3DcChroma(_iPreviousCb_DC)); - return; - default: - readV3DcLuma(); - code.setBottom10Bits(_iPreviousY_DC); + private static class QuantizationDcReader_STRv3 implements IQuantizationDc { + + private final int _iFrameQuantizationScale; + + /** Holds the previous DC values during a version 3 frame decoding. */ + private int _iPreviousCr_DC = 0, + _iPreviousCb_DC = 0, + _iPreviousY_DC = 0; + + public QuantizationDcReader_STRv3(int iFrameQuantizationScale) { + _iFrameQuantizationScale = iFrameQuantizationScale; } - } - - private int readV3DcChroma(int iPreviousDC) - throws MdecException.ReadCorruption, MdecException.EndOfStream - { - // Peek enough bits - int iBits = _bitReader.peekUnsignedBits(DC_CHROMA_LONGEST_VARIABLE_LENGTH_CODE); - - DcVariableLengthCode dcVlc = CHROMA_LOOKUP[iBits]; - - if (dcVlc == null) { - throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_UNKNOWN_CHROMA_DC_VLC( - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - Misc.bitsToString(iBits, DC_CHROMA_LONGEST_VARIABLE_LENGTH_CODE))); + public void readQuantizationScaleAndDc(@Nonnull ArrayBitReader bitReader, + @Nonnull MdecContext context, + @Nonnull MdecCode code) + throws MdecException.ReadCorruption, MdecException.EndOfStream + { + code.setTop6Bits(_iFrameQuantizationScale); + switch (context.getCurrentBlock()) { + case Cr: + code.setBottom10Bits(_iPreviousCr_DC = readV3DcChroma(_iPreviousCr_DC, bitReader, context)); + return; + case Cb: + code.setBottom10Bits(_iPreviousCb_DC = readV3DcChroma(_iPreviousCb_DC, bitReader, context)); + return; + default: + readV3DcLuma(bitReader, context); + code.setBottom10Bits(_iPreviousY_DC); + } } - assert !DEBUG || _debug.append(dcVlc.VariableLengthCode); - // skip the variable length code bits - _bitReader.skipBits(dcVlc.VariableLengthCode.length()); + private int readV3DcChroma(int iPreviousDC, @Nonnull ArrayBitReader bitReader, @Nonnull MdecContext context) + throws MdecException.ReadCorruption, MdecException.EndOfStream + { + // Peek enough bits + int iBits = bitReader.peekUnsignedBits(DC_CHROMA_LONGEST_VARIABLE_LENGTH_CODE); - iPreviousDC += dcVlc.readDc(_bitReader, _debug); + DcVariableLengthCode dcVlc = CHROMA_LOOKUP[iBits]; - if (iPreviousDC < -512 || iPreviousDC > 511) { - throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_CHROMA_DC_OOB( - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - iPreviousDC)); - } + if (dcVlc == null) { + throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_UNKNOWN_CHROMA_DC_VLC( + context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + Misc.bitsToString(iBits, DC_CHROMA_LONGEST_VARIABLE_LENGTH_CODE))); + } - return iPreviousDC; - } - - private void readV3DcLuma() throws MdecException.ReadCorruption, MdecException.EndOfStream { - // Peek enough bits - int iBits = _bitReader.peekUnsignedBits(DC_LUMA_LONGEST_VARIABLE_LENGTH_CODE); - - DcVariableLengthCode dcVlc = LUMA_LOOKUP[iBits]; - - if (dcVlc == null) { - throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_UNKNOWN_LUMA_DC_VLC( - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - Misc.bitsToString(iBits, DC_LUMA_LONGEST_VARIABLE_LENGTH_CODE))); + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(dcVlc.VariableLengthCode); + + // skip the variable length code bits + bitReader.skipBits(dcVlc.VariableLengthCode.length()); + + iPreviousDC += dcVlc.readDc(bitReader); + + if (iPreviousDC < -512 || iPreviousDC > 511) { + throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_CHROMA_DC_OOB( + context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + iPreviousDC)); + } + + return iPreviousDC; } - - assert !DEBUG || _debug.append(dcVlc.VariableLengthCode); - // skip the variable length code bits - _bitReader.skipBits(dcVlc.VariableLengthCode.length()); + private void readV3DcLuma(@Nonnull ArrayBitReader bitReader, @Nonnull MdecContext context) throws MdecException.ReadCorruption, MdecException.EndOfStream { + // Peek enough bits + int iBits = bitReader.peekUnsignedBits(DC_LUMA_LONGEST_VARIABLE_LENGTH_CODE); + + DcVariableLengthCode dcVlc = LUMA_LOOKUP[iBits]; + + if (dcVlc == null) { + throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_UNKNOWN_LUMA_DC_VLC( + context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + Misc.bitsToString(iBits, DC_LUMA_LONGEST_VARIABLE_LENGTH_CODE))); + } + + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.appendBits(dcVlc.VariableLengthCode); + + // skip the variable length code bits + bitReader.skipBits(dcVlc.VariableLengthCode.length()); - _iPreviousY_DC += dcVlc.readDc(_bitReader, _debug); + _iPreviousY_DC += dcVlc.readDc(bitReader); - if (_iPreviousY_DC < -512 || _iPreviousY_DC > 511) { - throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_LUMA_DC_OOB( - getFullMacroBlocksRead(), getCurrentMacroBlockSubBlock(), - _iPreviousY_DC)); + if (_iPreviousY_DC < -512 || _iPreviousY_DC > 511) { + throw new MdecException.ReadCorruption(MdecException.STRV3_BLOCK_UNCOMPRESS_ERR_LUMA_DC_OOB( + context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + _iPreviousY_DC)); + } } } - @Override - public void skipPaddingBits() throws MdecException.EndOfStream { - int iPaddingBits = _bitReader.readUnsignedBits(11); - if (iPaddingBits != b11111111110) { - LOG.log(Level.WARNING, "Incorrect padding bits {0}", Misc.bitsToString(iPaddingBits, 11)); + private static class FrameEndPaddingBits_STRv3 implements IFrameEndPaddingBits { + @Override + public void skipPaddingBits(@Nonnull ArrayBitReader bitReader) throws MdecException.EndOfStream { + int iPaddingBits = bitReader.readUnsignedBits(11); + if (iPaddingBits != b11111111110) { + LOG.log(Level.WARNING, "Incorrect padding bits {0}", Misc.bitsToString(iPaddingBits, 11)); + } } } + private static final FrameEndPaddingBits_STRv3 FRAME_END_PADDING_BITS_STRV3 = new FrameEndPaddingBits_STRv3(); - @Override - public @Nonnull Type getType() { - return Type.STRv3; - } - @Override public @Nonnull BitStreamCompressor_STRv3 makeCompressor() { - return new BitStreamCompressor_STRv3(getFullMacroBlocksRead()); + return new BitStreamCompressor_STRv3(_context.getTotalMacroBlocksRead()); } // ========================================================================= /** Note unlike all other compressors, STRv3 is LOSSY when encoding * DC coefficients. */ - public static class BitStreamCompressor_STRv3 extends BitStreamCompressor_STRv2 { + public static class BitStreamCompressor_STRv3 extends BitStreamUncompressor_STRv2.BitStreamCompressor_STRv2 { BitStreamCompressor_STRv3(int iMacroBlockCount) { super(iMacroBlockCount); @@ -402,8 +424,8 @@ public static class BitStreamCompressor_STRv3 extends BitStreamCompressor_STRv2 protected int getFrameQscale(@Nonnull byte[] abFrameData) throws LocalizedIncompatibleException { StrV3Header header = new StrV3Header(abFrameData, abFrameData.length); if (!header.isValid()) - throw new LocalizedIncompatibleException(I.FRAME_NOT_STRV3()); - return header.getQscale(); + throw new LocalizedIncompatibleException(I.FRAME_IS_NOT_BITSTREAM_FORMAT("STRv3")); + return header.getQuantizationScale(); } @@ -424,7 +446,7 @@ protected int getFrameQscale(@Nonnull byte[] abFrameData) throws LocalizedIncomp _iPreviousY_DcRound4; @Override - protected @Nonnull String encodeDC(int iDC, int iBlock) throws MdecException.TooMuchEnergy { + protected @Nonnull String encodeDC(int iDC, @Nonnull MdecBlock block) throws MdecException.TooMuchEnergy { // round to the nearest multiple of 4 // TODO: Maybe try to expose this quality loss somehow int iDcRound4 = (int)Math.round(iDC / 4.0) * 4; @@ -434,13 +456,13 @@ protected int getFrameQscale(@Nonnull byte[] abFrameData) throws LocalizedIncomp // and save the rounded DC for the next iteration DcVariableLengthCode[] lookupTable; int iDcDiffRound4Div4; - switch (iBlock) { - case 0: + switch (block) { + case Cr: iDcDiffRound4Div4 = (iDcRound4 - _iPreviousCr_DcRound4) / 4; _iPreviousCr_DcRound4 = iDcRound4; lookupTable = DC_Chroma_VarLenCodes; break; - case 1: + case Cb: iDcDiffRound4Div4 = (iDcRound4 - _iPreviousCb_DcRound4) / 4; _iPreviousCb_DcRound4 = iDcRound4; lookupTable = DC_Chroma_VarLenCodes; diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/StrHeader.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/StrHeader.java new file mode 100644 index 0000000..1b1feb2 --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/StrHeader.java @@ -0,0 +1,104 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.psxvideo.bitstreams; + +import javax.annotation.Nonnull; +import jpsxdec.util.BinaryDataNotRecognized; +import jpsxdec.util.IO; + +public abstract class StrHeader { + + public static final int SIZEOF = 8; + + /** Frame's quantization scale. */ + private int _iQuantizationScale = -1; + private int _iHalfVlcCountCeil32 = -1; + + private final boolean _blnIsValid; + protected StrHeader(@Nonnull byte[] abFrameData, int iDataSize, + int iExpectedVersion) + { + if (iDataSize < 8) { + _blnIsValid = false; + } else { + int iHalfVlcCountCeil32 = IO.readSInt16LE(abFrameData, 0); + int iMagic3800 = IO.readUInt16LE(abFrameData, 2); + int iQscale = IO.readSInt16LE(abFrameData, 4); + int iVersion = IO.readSInt16LE(abFrameData, 6); + + _blnIsValid = iMagic3800 == 0x3800 && + iQscale >= 1 && + iVersion == iExpectedVersion && + iHalfVlcCountCeil32 >= 0; + if (_blnIsValid) { + _iQuantizationScale = iQscale; + _iHalfVlcCountCeil32 = iHalfVlcCountCeil32; + } + } + } + + public int getQuantizationScale() { + if (!_blnIsValid) throw new IllegalStateException(); + return _iQuantizationScale; + } + + public int getHalfVlcCountCeil32() { + if (!_blnIsValid) throw new IllegalStateException(); + return _iHalfVlcCountCeil32; + } + + public boolean isValid() { + return _blnIsValid; + } + + public @Nonnull BitStreamUncompressor makeNew(@Nonnull byte[] abBitstream) throws BinaryDataNotRecognized { + return makeNew(abBitstream, abBitstream.length); + } + + abstract public @Nonnull BitStreamUncompressor makeNew(@Nonnull byte[] abBitstream, int iBitstreamSize) + throws BinaryDataNotRecognized; + + @Override + public String toString() { + if (_blnIsValid) + return "Qscale " + _iQuantizationScale + " count " + _iHalfVlcCountCeil32; + else + return "Invalid STR header"; + } + +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAc.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAc.java new file mode 100644 index 0000000..06fed3e --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAc.java @@ -0,0 +1,144 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.psxvideo.bitstreams; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecCode; + +/** Assigns value to a {@link BitStreamCode}. The value can have a + * zero-run-length and AC coefficient (i.e. {@link MdecCode}) + * and/or the escape code or end-of-block code. */ +public class ZeroRunLengthAc { + + @Nonnull + private final BitStreamCode _bitStreamCode; + + @CheckForNull + private final MdecCode _mdecCode; + + private final boolean _blnIsEscapeCode; + private final boolean _blnIsEndOfBlock; + + /* If this is a normal AC code with MDEC code equivalent. */ + public ZeroRunLengthAc(@Nonnull BitStreamCode bitStreamCode, int iZeroRunLength, int iAcCoefficient) { + _bitStreamCode = bitStreamCode; + _mdecCode = new MdecCode(iZeroRunLength, iAcCoefficient); + _blnIsEscapeCode = false; + _blnIsEndOfBlock = false; + } + + /* If this is a special AC code without an MDEC equialent. */ + public ZeroRunLengthAc(@Nonnull BitStreamCode bitStreamCode, boolean blnIsEscapeCode, boolean blnIsEndOfBlock) { + this(bitStreamCode, null, blnIsEscapeCode, blnIsEndOfBlock); + } + + /* If this is a special AC code with an MDEC equialent. */ + public ZeroRunLengthAc(@Nonnull BitStreamCode bitStreamCode, + int iZeroRunLength, int iAcCoefficient, + boolean blnIsEscapeCode, boolean blnIsEndOfBlock) + { + this(bitStreamCode, new MdecCode(iZeroRunLength, iAcCoefficient), blnIsEscapeCode, blnIsEndOfBlock); + } + + /* If this is a special AC code with an MDEC equialent. */ + private ZeroRunLengthAc(@Nonnull BitStreamCode bitStreamCode, + @CheckForNull MdecCode mdecCode, + boolean blnIsEscapeCode, boolean blnIsEndOfBlock) + { + if (blnIsEscapeCode && blnIsEndOfBlock) + throw new IllegalArgumentException("Only one of [escape code] or [end of block] can be true"); + _bitStreamCode = bitStreamCode; + _mdecCode = mdecCode; + _blnIsEscapeCode = blnIsEscapeCode; + _blnIsEndOfBlock = blnIsEndOfBlock; + } + + public void getMdecCode(@Nonnull MdecCode out) { + if (_mdecCode == null) { + throw new IllegalStateException("MDEC code requested from AC without code " + this); + } else { + out.setFrom(_mdecCode); + } + } + + public @CheckForNull MdecCode getMdecCodeCopy() { + if (_mdecCode == null) + return null; + else + return _mdecCode.copy(); + } + + public @Nonnull BitStreamCode getBitStreamCode() { + return _bitStreamCode; + } + + public @Nonnull String getBitString() { + return _bitStreamCode.getString(); + } + + public int getBitLength() { + return _bitStreamCode.getLength(); + } + + public boolean isIsEscapeCode() { + return _blnIsEscapeCode; + } + + public boolean isIsEndOfBlock() { + return _blnIsEndOfBlock; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(_bitStreamCode); + if (_mdecCode != null) + sb.append(' ').append(_mdecCode); + if (_blnIsEscapeCode) + sb.append(" ESCAPE_CODE"); + if (_blnIsEndOfBlock) + sb.append(" END_OF_BLOCK"); + return sb.toString(); + } + + public boolean equalsMdec(@Nonnull MdecCode code) { + return _mdecCode != null && _mdecCode.equals(code); + } + +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup.java new file mode 100644 index 0000000..93a1dbf --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup.java @@ -0,0 +1,527 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.psxvideo.bitstreams; + +import java.io.PrintStream; +import java.util.Iterator; +import java.util.TreeSet; +import javax.annotation.Nonnull; +import static jpsxdec.psxvideo.bitstreams.BitStreamCode.*; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecException; +import jpsxdec.util.Misc; + +/** Creates ultra fast bit-stream lookup tables. */ +public class ZeroRunLengthAcLookup implements Iterable { + + public static class Builder { + + private final ZeroRunLengthAc[] _aoList = new ZeroRunLengthAc[BitStreamCode.getTotalCount()]; + + private final TreeSet _duplicateCodeChecker = new TreeSet(); + + /** To set the zero run-length and AC coefficient for a single bit stream code. */ + public @Nonnull Builder set(@Nonnull BitStreamCode bitStreamCode, int iZeroRunLength, int iAcCoefficient) { + return add(new ZeroRunLengthAc(bitStreamCode, iZeroRunLength, iAcCoefficient)); + } + + /** To set the zero run-length and AC coefficient for a single bit stream code. */ + public @Nonnull Builder add(@Nonnull ZeroRunLengthAc zrlac) { + MdecCode mdecCode = zrlac.getMdecCodeCopy(); + if (mdecCode != null) { + if (!_duplicateCodeChecker.add(mdecCode)) + throw new RuntimeException("Already got MDEC code " + mdecCode); + } + int iIndex = zrlac.getBitStreamCode().ordinal(); + if (_aoList[iIndex] != null) + throw new RuntimeException("Trying to replace " + _aoList[iIndex] + " with " + zrlac + " at " + iIndex); + _aoList[iIndex] = zrlac; + return this; + } + + // To set the zero run-length and AC coefficient for the 2 codes covered with a sign bit. + // + public Builder _11s (int zr, int aac) { return set(_110______________, _111______________, zr, aac); } + // ------------------------------------------------------------------------------ + public Builder _011s (int zr, int aac) { return set(_0110_____________, _0111_____________, zr, aac); } + public Builder _0100s (int zr, int aac) { return set(_01000____________, _01001____________, zr, aac); } + public Builder _0101s (int zr, int aac) { return set(_01010____________, _01011____________, zr, aac); } + public Builder _00101s (int zr, int aac) { return set(_001010___________, _001011___________, zr, aac); } + public Builder _00110s (int zr, int aac) { return set(_001100___________, _001101___________, zr, aac); } + public Builder _00111s (int zr, int aac) { return set(_001110___________, _001111___________, zr, aac); } + public Builder _000100s (int zr, int aac) { return set(_0001000__________, _0001001__________, zr, aac); } + public Builder _000101s (int zr, int aac) { return set(_0001010__________, _0001011__________, zr, aac); } + public Builder _000110s (int zr, int aac) { return set(_0001100__________, _0001101__________, zr, aac); } + public Builder _000111s (int zr, int aac) { return set(_0001110__________, _0001111__________, zr, aac); } + public Builder _0000100s (int zr, int aac) { return set(_00001000_________, _00001001_________, zr, aac); } + public Builder _0000101s (int zr, int aac) { return set(_00001010_________, _00001011_________, zr, aac); } + public Builder _0000110s (int zr, int aac) { return set(_00001100_________, _00001101_________, zr, aac); } + public Builder _0000111s (int zr, int aac) { return set(_00001110_________, _00001111_________, zr, aac); } + public Builder _00100000s(int zr, int aac) { return set(_001000000________, _001000001________, zr, aac); } + public Builder _00100001s(int zr, int aac) { return set(_001000010________, _001000011________, zr, aac); } + public Builder _00100010s(int zr, int aac) { return set(_001000100________, _001000101________, zr, aac); } + public Builder _00100011s(int zr, int aac) { return set(_001000110________, _001000111________, zr, aac); } + public Builder _00100100s(int zr, int aac) { return set(_001001000________, _001001001________, zr, aac); } + public Builder _00100101s(int zr, int aac) { return set(_001001010________, _001001011________, zr, aac); } + public Builder _00100110s(int zr, int aac) { return set(_001001100________, _001001101________, zr, aac); } + public Builder _00100111s(int zr, int aac) { return set(_001001110________, _001001111________, zr, aac); } + // ------------------------------------------------------------------------------ + public Builder _0000001000s(int zr, int aac) { return set(_00000010000______, _00000010001______, zr, aac); } + public Builder _0000001001s(int zr, int aac) { return set(_00000010010______, _00000010011______, zr, aac); } + public Builder _0000001010s(int zr, int aac) { return set(_00000010100______, _00000010101______, zr, aac); } + public Builder _0000001011s(int zr, int aac) { return set(_00000010110______, _00000010111______, zr, aac); } + public Builder _0000001100s(int zr, int aac) { return set(_00000011000______, _00000011001______, zr, aac); } + public Builder _0000001101s(int zr, int aac) { return set(_00000011010______, _00000011011______, zr, aac); } + public Builder _0000001110s(int zr, int aac) { return set(_00000011100______, _00000011101______, zr, aac); } + public Builder _0000001111s(int zr, int aac) { return set(_00000011110______, _00000011111______, zr, aac); } + public Builder _000000010000s(int zr, int aac) { return set(_0000000100000____, _0000000100001____, zr, aac); } + public Builder _000000010001s(int zr, int aac) { return set(_0000000100010____, _0000000100011____, zr, aac); } + public Builder _000000010010s(int zr, int aac) { return set(_0000000100100____, _0000000100101____, zr, aac); } + public Builder _000000010011s(int zr, int aac) { return set(_0000000100110____, _0000000100111____, zr, aac); } + public Builder _000000010100s(int zr, int aac) { return set(_0000000101000____, _0000000101001____, zr, aac); } + public Builder _000000010101s(int zr, int aac) { return set(_0000000101010____, _0000000101011____, zr, aac); } + public Builder _000000010110s(int zr, int aac) { return set(_0000000101100____, _0000000101101____, zr, aac); } + public Builder _000000010111s(int zr, int aac) { return set(_0000000101110____, _0000000101111____, zr, aac); } + public Builder _000000011000s(int zr, int aac) { return set(_0000000110000____, _0000000110001____, zr, aac); } + public Builder _000000011001s(int zr, int aac) { return set(_0000000110010____, _0000000110011____, zr, aac); } + public Builder _000000011010s(int zr, int aac) { return set(_0000000110100____, _0000000110101____, zr, aac); } + public Builder _000000011011s(int zr, int aac) { return set(_0000000110110____, _0000000110111____, zr, aac); } + public Builder _000000011100s(int zr, int aac) { return set(_0000000111000____, _0000000111001____, zr, aac); } + public Builder _000000011101s(int zr, int aac) { return set(_0000000111010____, _0000000111011____, zr, aac); } + public Builder _000000011110s(int zr, int aac) { return set(_0000000111100____, _0000000111101____, zr, aac); } + public Builder _000000011111s(int zr, int aac) { return set(_0000000111110____, _0000000111111____, zr, aac); } + public Builder _0000000010000s(int zr, int aac) { return set(_00000000100000___, _00000000100001___, zr, aac); } + public Builder _0000000010001s(int zr, int aac) { return set(_00000000100010___, _00000000100011___, zr, aac); } + public Builder _0000000010010s(int zr, int aac) { return set(_00000000100100___, _00000000100101___, zr, aac); } + public Builder _0000000010011s(int zr, int aac) { return set(_00000000100110___, _00000000100111___, zr, aac); } + public Builder _0000000010100s(int zr, int aac) { return set(_00000000101000___, _00000000101001___, zr, aac); } + public Builder _0000000010101s(int zr, int aac) { return set(_00000000101010___, _00000000101011___, zr, aac); } + public Builder _0000000010110s(int zr, int aac) { return set(_00000000101100___, _00000000101101___, zr, aac); } + public Builder _0000000010111s(int zr, int aac) { return set(_00000000101110___, _00000000101111___, zr, aac); } + public Builder _0000000011000s(int zr, int aac) { return set(_00000000110000___, _00000000110001___, zr, aac); } + public Builder _0000000011001s(int zr, int aac) { return set(_00000000110010___, _00000000110011___, zr, aac); } + public Builder _0000000011010s(int zr, int aac) { return set(_00000000110100___, _00000000110101___, zr, aac); } + public Builder _0000000011011s(int zr, int aac) { return set(_00000000110110___, _00000000110111___, zr, aac); } + public Builder _0000000011100s(int zr, int aac) { return set(_00000000111000___, _00000000111001___, zr, aac); } + public Builder _0000000011101s(int zr, int aac) { return set(_00000000111010___, _00000000111011___, zr, aac); } + public Builder _0000000011110s(int zr, int aac) { return set(_00000000111100___, _00000000111101___, zr, aac); } + public Builder _0000000011111s(int zr, int aac) { return set(_00000000111110___, _00000000111111___, zr, aac); } + // ------------------------------------------------------------------------------ + public Builder _00000000010000s(int zr, int aac) { return set(_000000000100000__, _000000000100001__, zr, aac); } + public Builder _00000000010001s(int zr, int aac) { return set(_000000000100010__, _000000000100011__, zr, aac); } + public Builder _00000000010010s(int zr, int aac) { return set(_000000000100100__, _000000000100101__, zr, aac); } + public Builder _00000000010011s(int zr, int aac) { return set(_000000000100110__, _000000000100111__, zr, aac); } + public Builder _00000000010100s(int zr, int aac) { return set(_000000000101000__, _000000000101001__, zr, aac); } + public Builder _00000000010101s(int zr, int aac) { return set(_000000000101010__, _000000000101011__, zr, aac); } + public Builder _00000000010110s(int zr, int aac) { return set(_000000000101100__, _000000000101101__, zr, aac); } + public Builder _00000000010111s(int zr, int aac) { return set(_000000000101110__, _000000000101111__, zr, aac); } + public Builder _00000000011000s(int zr, int aac) { return set(_000000000110000__, _000000000110001__, zr, aac); } + public Builder _00000000011001s(int zr, int aac) { return set(_000000000110010__, _000000000110011__, zr, aac); } + public Builder _00000000011010s(int zr, int aac) { return set(_000000000110100__, _000000000110101__, zr, aac); } + public Builder _00000000011011s(int zr, int aac) { return set(_000000000110110__, _000000000110111__, zr, aac); } + public Builder _00000000011100s(int zr, int aac) { return set(_000000000111000__, _000000000111001__, zr, aac); } + public Builder _00000000011101s(int zr, int aac) { return set(_000000000111010__, _000000000111011__, zr, aac); } + public Builder _00000000011110s(int zr, int aac) { return set(_000000000111100__, _000000000111101__, zr, aac); } + public Builder _00000000011111s(int zr, int aac) { return set(_000000000111110__, _000000000111111__, zr, aac); } + public Builder _000000000010000s(int zr, int aac) { return set(_0000000000100000_, _0000000000100001_, zr, aac); } + public Builder _000000000010001s(int zr, int aac) { return set(_0000000000100010_, _0000000000100011_, zr, aac); } + public Builder _000000000010010s(int zr, int aac) { return set(_0000000000100100_, _0000000000100101_, zr, aac); } + public Builder _000000000010011s(int zr, int aac) { return set(_0000000000100110_, _0000000000100111_, zr, aac); } + public Builder _000000000010100s(int zr, int aac) { return set(_0000000000101000_, _0000000000101001_, zr, aac); } + public Builder _000000000010101s(int zr, int aac) { return set(_0000000000101010_, _0000000000101011_, zr, aac); } + public Builder _000000000010110s(int zr, int aac) { return set(_0000000000101100_, _0000000000101101_, zr, aac); } + public Builder _000000000010111s(int zr, int aac) { return set(_0000000000101110_, _0000000000101111_, zr, aac); } + public Builder _000000000011000s(int zr, int aac) { return set(_0000000000110000_, _0000000000110001_, zr, aac); } + public Builder _000000000011001s(int zr, int aac) { return set(_0000000000110010_, _0000000000110011_, zr, aac); } + public Builder _000000000011010s(int zr, int aac) { return set(_0000000000110100_, _0000000000110101_, zr, aac); } + public Builder _000000000011011s(int zr, int aac) { return set(_0000000000110110_, _0000000000110111_, zr, aac); } + public Builder _000000000011100s(int zr, int aac) { return set(_0000000000111000_, _0000000000111001_, zr, aac); } + public Builder _000000000011101s(int zr, int aac) { return set(_0000000000111010_, _0000000000111011_, zr, aac); } + public Builder _000000000011110s(int zr, int aac) { return set(_0000000000111100_, _0000000000111101_, zr, aac); } + public Builder _000000000011111s(int zr, int aac) { return set(_0000000000111110_, _0000000000111111_, zr, aac); } + public Builder _0000000000010000s(int zr, int aac) { return set(_00000000000100000, _00000000000100001, zr, aac); } + public Builder _0000000000010001s(int zr, int aac) { return set(_00000000000100010, _00000000000100011, zr, aac); } + public Builder _0000000000010010s(int zr, int aac) { return set(_00000000000100100, _00000000000100101, zr, aac); } + public Builder _0000000000010011s(int zr, int aac) { return set(_00000000000100110, _00000000000100111, zr, aac); } + public Builder _0000000000010100s(int zr, int aac) { return set(_00000000000101000, _00000000000101001, zr, aac); } + public Builder _0000000000010101s(int zr, int aac) { return set(_00000000000101010, _00000000000101011, zr, aac); } + public Builder _0000000000010110s(int zr, int aac) { return set(_00000000000101100, _00000000000101101, zr, aac); } + public Builder _0000000000010111s(int zr, int aac) { return set(_00000000000101110, _00000000000101111, zr, aac); } + public Builder _0000000000011000s(int zr, int aac) { return set(_00000000000110000, _00000000000110001, zr, aac); } + public Builder _0000000000011001s(int zr, int aac) { return set(_00000000000110010, _00000000000110011, zr, aac); } + public Builder _0000000000011010s(int zr, int aac) { return set(_00000000000110100, _00000000000110101, zr, aac); } + public Builder _0000000000011011s(int zr, int aac) { return set(_00000000000110110, _00000000000110111, zr, aac); } + public Builder _0000000000011100s(int zr, int aac) { return set(_00000000000111000, _00000000000111001, zr, aac); } + public Builder _0000000000011101s(int zr, int aac) { return set(_00000000000111010, _00000000000111011, zr, aac); } + public Builder _0000000000011110s(int zr, int aac) { return set(_00000000000111100, _00000000000111101, zr, aac); } + public Builder _0000000000011111s(int zr, int aac) { return set(_00000000000111110, _00000000000111111, zr, aac); } + + private @Nonnull Builder set(@Nonnull BitStreamCode positiveBitStreamCodeEndsWith0, + @Nonnull BitStreamCode negitiveBitStreamCodeEndsWith1, + int iZeroRunLength, int iAbsoluteAcCoefficient) + { + set(positiveBitStreamCodeEndsWith0, iZeroRunLength, iAbsoluteAcCoefficient); + set(negitiveBitStreamCodeEndsWith1, iZeroRunLength, -iAbsoluteAcCoefficient); + return this; + } + + public @Nonnull ZeroRunLengthAcLookup build() { + return new ZeroRunLengthAcLookup(_aoList); + } + + } + + @Nonnull + private final ZeroRunLengthAc[] _aoList; + + /** Table to look up '10' and '11s' codes using all but the first (1) bit. */ + private final ZeroRunLengthAc[] _aoTable_1xx = new ZeroRunLengthAc[4]; + /** Table to look up codes '011s' to '00100111s' using all but the first (0) bit. + * Given a bit code in that range, strip the leading zero bit, then pad + * any extra trailing bits to make an 8 bit value. Use that value as the + * index in this table to get the corresponding code. */ + private final ZeroRunLengthAc[] _aoTable_0xxxxxxx = new ZeroRunLengthAc[256]; + /** Table to look up codes '0000001000s' to '0000000011111s' using all but + * the first 6 zero bits. + * Given a bit code in that range, strip the leading 6 zero bits, then pad + * any extra trailing bits to make an 8 bit value. Use that value as the + * index in this table to get the corresponding code. */ + private final ZeroRunLengthAc[] _aoTable_000000xxxxxxxx = new ZeroRunLengthAc[256]; + /** Table to look up codes '00000000010000s' to '0000000000011111s' using + * all but the first 9 zero bits. + * Given a bit code in that range, strip the leading 9 zero bits, then pad + * any extra trailing bits to make an 8 bit value. Use that value as the + * index in this table to get the corresponding code. */ + private final ZeroRunLengthAc[] _aoTable_000000000xxxxxxxx = new ZeroRunLengthAc[256]; + + private ZeroRunLengthAcLookup(@Nonnull ZeroRunLengthAc[] aoList) { + _aoList = aoList; + for (int i = 0; i < aoList.length; i++) { + ZeroRunLengthAc zrlac = aoList[i]; + BitStreamCode bitStreamCode = BitStreamCode.get(i); + if (zrlac == null) + throw new IllegalStateException("Table incomplete: missing " + bitStreamCode); + setBits(bitStreamCode, zrlac); + } + } + + /** Identifies the lookup table in which to place the bit code. */ + private void setBits(@Nonnull BitStreamCode bsc, @Nonnull ZeroRunLengthAc zrlac) { + final int iBitsRemain; + final ZeroRunLengthAc[] aoTable; + final int iTableStart; + + // This needs some explaination + if (bsc.getString().startsWith("000000000")) { + aoTable = _aoTable_000000000xxxxxxxx; + iBitsRemain = 8 - (bsc.getLength() - 9); + iTableStart = Integer.parseInt(bsc.getString(), 2) << iBitsRemain; + } else if (bsc.getString().startsWith("000000" )) { + aoTable = _aoTable_000000xxxxxxxx; + iBitsRemain = 8 - (bsc.getLength() - 6); + iTableStart = Integer.parseInt(bsc.getString(), 2) << iBitsRemain; + } else if (bsc.getString().startsWith("0" )) { + aoTable = _aoTable_0xxxxxxx; + iBitsRemain = 8 - (bsc.getLength() - 1); + iTableStart = Integer.parseInt(bsc.getString(), 2) << iBitsRemain; + } else { // startsWith("1") + aoTable = _aoTable_1xx; + iBitsRemain = 2 - (bsc.getLength() - 1); + iTableStart = Integer.parseInt(bsc.getString().substring(1), 2) << iBitsRemain; + } + + final int iTableEntriesToAssociate = (1 << iBitsRemain); + for (int i = 0; i < iTableEntriesToAssociate; i++) { + if (aoTable[iTableStart + i] != null) + throw new RuntimeException("Trying to replace " + aoTable[iTableStart + i] + + " with " + zrlac); + aoTable[iTableStart + i] = zrlac; + } + } + + // ######################################################################### + + public @Nonnull Iterator iterator() { + return new Iterator() { + private int _i = 0; + public boolean hasNext() { + return _i < _aoList.length; + } + + public @Nonnull ZeroRunLengthAc next() { + return _aoList[_i++]; + } + + public void remove() { + throw new UnsupportedOperationException("List is immutable"); + } + }; + } + + /** Useful bitmasks. */ + private static final int + b11000000000000000 = 0x18000, + b10000000000000000 = 0x10000, + b01000000000000000 = 0x08000, + b00100000000000000 = 0x04000, + b01111100000000000 = 0x0F800, + b00000011100000000 = 0x00700, + b00000000011100000 = 0x000E0; + + /** Converts bits to the equivalent {@link BitstreamToMdecLookup}. + * 17 bits need to be supplied to decode (the longest bit code). + * Bits should start at the least-significant bit. + * Bits beyond the 17 least-significant bits are ignored. + * If a full 17 bits are unavailable, fill the remaining with zeros + * to ensure failure if bit code is invalid. + * + * @param i17bits Integer containing 17 bits to decode. + */ + public @Nonnull ZeroRunLengthAc lookup(final int i17bits) throws MdecException.ReadCorruption { + if ((i17bits & b10000000000000000) != 0) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println("Table 0 offset " + ((i17bits >> 14) & 3)); + return _aoTable_1xx[(i17bits >> 14) & 3]; + } else if ((i17bits & b01111100000000000) != 0) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println("Table 1 offset " + ((i17bits >> 8) & 0xff)); + return _aoTable_0xxxxxxx[(i17bits >> 8) & 0xff]; + } else if ((i17bits & b00000011100000000) != 0) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println("Table 2 offset " + ((i17bits >> 3) & 0xff)); + return _aoTable_000000xxxxxxxx[(i17bits >> 3) & 0xff]; + } else if ((i17bits & b00000000011100000) != 0) { + assert !BitStreamDebugging.DEBUG || BitStreamDebugging.println("Table 3 offset " + (i17bits & 0xff)); + return _aoTable_000000000xxxxxxxx[i17bits & 0xff]; + } else { + throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); + } + } + + private static @Nonnull String UNMATCHED_AC_VLC(int i17bits) { + return "Unmatched AC variable length code: " + + Misc.bitsToString(i17bits, LONGEST_BITSTREAM_CODE_17BITS); + } + + // .................................................. + + /** Alternative lookup method that is slower that {@link #lookup(int)}. */ + public @Nonnull ZeroRunLengthAc lookup_slow1(final int i17bits) throws MdecException.ReadCorruption { + if ((i17bits & b11000000000000000) == b10000000000000000) { + return _aoList[_10_______________.ordinal()]; + } else if ((i17bits & b11000000000000000) == b11000000000000000) { + // special handling for first AC code + if ((i17bits & b00100000000000000) == 0) // check sign bit + return _aoList[_110______________.ordinal()]; + else + return _aoList[_111______________.ordinal()]; + } else { + // escape code is already set in the lookup table + final ZeroRunLengthAc[] array; + final int c; + if ((i17bits & b01111100000000000) != 0) { + array = _aoTable_0xxxxxxx; + c = (i17bits >> 8) & 0xff; + } else if ((i17bits & b00000011100000000) != 0) { + array = _aoTable_000000xxxxxxxx; + c = (i17bits >> 3) & 0xff; + } else if ((i17bits & b00000000011100000) != 0) { + array = _aoTable_000000000xxxxxxxx; + c = i17bits & 0xff; + } else { + throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); + } + return array[c]; + } + } + + /** Alternative lookup method that is slower that {@link #lookup(int)}. */ + public @Nonnull ZeroRunLengthAc lookup_slow2(final int i17bits) throws MdecException.ReadCorruption { + if ((i17bits & b11000000000000000) == b10000000000000000) { + return _aoList[_10_______________.ordinal()]; + } else if ((i17bits & b11000000000000000) == b11000000000000000) { + // special handling for first AC code + if ((i17bits & b00100000000000000) == 0) // check sign bit + return _aoList[_110______________.ordinal()]; + else + return _aoList[_111______________.ordinal()]; + } else { + // escape code is already part of the lookup table + if ((i17bits & b01111100000000000) != 0) { + return _aoTable_0xxxxxxx[(i17bits >> 8) & 0xff]; + } else if ((i17bits & b00000011100000000) != 0) { + return _aoTable_000000xxxxxxxx[(i17bits >> 3) & 0xff]; + } else if ((i17bits & b00000000011100000) != 0) { + return _aoTable_000000000xxxxxxxx[i17bits & 0xff]; + } else { + throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); + } + } + } + + /** Alternative lookup method that is slower that {@link #lookup(int)}. */ + public @Nonnull ZeroRunLengthAc lookup_slow3(final int i17bits) throws MdecException.ReadCorruption { + if ((i17bits & b10000000000000000) != 0) { // 1st bit is 1? + if ((i17bits & b01000000000000000) == 0) { // initial bits == '10'? + return _aoList[_10_______________.ordinal()]; + } else { // initial bits == '11' + // special handling for first AC code + // check sign bit + if ((i17bits & b00100000000000000) == 0) // initial bits == '110'? + return _aoList[_110______________.ordinal()]; + else // initial bits == '111' + return _aoList[_111______________.ordinal()]; + } + } else { // 1st bit is 0 + // escape code is already set in the lookup table + final ZeroRunLengthAc[] array; + final int c; + if ((i17bits & b01111100000000000) != 0) { + array = _aoTable_0xxxxxxx; + c = (i17bits >> 8) & 0xff; + } else if ((i17bits & b00000011100000000) != 0) { + array = _aoTable_000000xxxxxxxx; + c = (i17bits >> 3) & 0xff; + } else if ((i17bits & b00000000011100000) != 0) { + array = _aoTable_000000000xxxxxxxx; + c = i17bits & 0xff; + } else { + throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); + } + return array[c]; + } + } + + /** Bit masks for + * {@link #lookup_old(int)} */ + private static final int + b1000000000000000_ = 0x8000 << 1, + b0100000000000000_ = 0x4000 << 1, + b0010000000000000_ = 0x2000 << 1, + b0001100000000000_ = 0x1800 << 1, + b0001000000000000_ = 0x1000 << 1, + b0000100000000000_ = 0x0800 << 1, + b0000010000000000_ = 0x0400 << 1, + b0000001000000000_ = 0x0200 << 1, + b0000000100000000_ = 0x0100 << 1, + b0000000010000000_ = 0x0080 << 1, + b0000000001000000_ = 0x0040 << 1, + b0000000000100000_ = 0x0020 << 1, + b0000000000010000_ = 0x0010 << 1; + + /** This lookup approach was used in 0.96.0 and earlier. */ + public @Nonnull ZeroRunLengthAc lookup_old(final int i17bits) throws MdecException.ReadCorruption { + final BitStreamCode vlc; + + // Walk through the bits, one-by-one + // Fun fact: The Lain Playstation game uses this same decoding approach + if ( (i17bits & b1000000000000000_) != 0) { // "1" + if ((i17bits & b0100000000000000_) != 0) { // "11" + vlc = BitStreamCode.get(0); + } else { // "10" + // End of block + return _aoList[_10_______________.ordinal()]; + } + } else if ((i17bits & b0100000000000000_) != 0) { // "01" + if ((i17bits & b0010000000000000_) != 0) { // "011" + vlc = BitStreamCode.get(1); + } else { // "010x" + vlc = BitStreamCode.get(2 + (int)((i17bits >>> 13) & 1)); + } + } else if ((i17bits & b0010000000000000_) != 0) { // "001" + if ((i17bits & b0001100000000000_) != 0) { // "001xx" + vlc = BitStreamCode.get(3 + (int)((i17bits >>> 12) & 3)); + } else { // "00100xxx" + vlc = BitStreamCode.get(15 + (int)((i17bits >>> 9) & 7)); + } + } else if ((i17bits & b0001000000000000_) != 0) { // "0001xx" + vlc = BitStreamCode.get(7 + (int)((i17bits >>> 11) & 3)); + } else if ((i17bits & b0000100000000000_) != 0) { // "00001xx" + vlc = BitStreamCode.get(11 + (int)((i17bits >>> 10) & 3)); + } else if ((i17bits & b0000010000000000_) != 0) { // "000001" + // escape code + return _aoList[_000001___________.ordinal()]; + } else if ((i17bits & b0000001000000000_) != 0) { // "0000001xxx" + vlc = BitStreamCode.get(23 + (int)((i17bits >>> 7) & 7)); + } else if ((i17bits & b0000000100000000_) != 0) { // "00000001xxxx" + vlc = BitStreamCode.get(31 + (int)((i17bits >>> 5) & 15)); + } else if ((i17bits & b0000000010000000_) != 0) { // "000000001xxxx" + vlc = BitStreamCode.get(47 + (int)((i17bits >>> 4) & 15)); + } else if ((i17bits & b0000000001000000_) != 0) { // "0000000001xxxx" + vlc = BitStreamCode.get(63 + (int)((i17bits >>> 3) & 15)); + } else if ((i17bits & b0000000000100000_) != 0) { // "00000000001xxxx" + vlc = BitStreamCode.get(79 + (int)((i17bits >>> 2) & 15)); + } else if ((i17bits & b0000000000010000_) != 0) { // "000000000001xxxx" + vlc = BitStreamCode.get(95 + (int)((i17bits >>> 1) & 15)); + } else { + throw new MdecException.ReadCorruption(UNMATCHED_AC_VLC(i17bits)); + } + + // Take either the positive (0) or negitive (1) AC coefficient, + // depending on the sign bit + if ((i17bits & (1 << (16 - vlc.getLength()))) == 0) { + // positive + return _aoList[vlc.ordinal()]; + } else { + // negative + return _aoList[vlc.ordinal() + 1]; + } + } + + public @Nonnull ZeroRunLengthAc lookup(@Nonnull String sBits) throws IllegalArgumentException { + int i = Integer.parseInt(sBits, 2); + i <<= LONGEST_BITSTREAM_CODE_17BITS - sBits.length(); + try { + return lookup(i); + } catch (MdecException.ReadCorruption ex) { + throw new IllegalArgumentException(ex); + } + } + + public void printAllLookupTables(@Nonnull PrintStream ps) { + for (int i = 0; i < _aoTable_1xx.length; i++) { + ps.println("Table_1xx["+i+"] = "+_aoTable_1xx[i]); + } + for (int i = 0; i < _aoTable_0xxxxxxx.length; i++) { + ps.println("Table_0xxxxxxx["+i+"] = "+_aoTable_0xxxxxxx[i]); + } + for (int i = 0; i < _aoTable_000000xxxxxxxx.length; i++) { + ps.println("Table_000000xxxxxxxx["+i+"] = "+_aoTable_000000xxxxxxxx[i]); + } + for (int i = 0; i < _aoTable_000000000xxxxxxxx.length; i++) { + ps.println("Table_000000000xxxxxxxx["+i+"] = "+_aoTable_000000000xxxxxxxx[i]); + } + } +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup_STR.java b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup_STR.java new file mode 100644 index 0000000..1be77d6 --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/ZeroRunLengthAcLookup_STR.java @@ -0,0 +1,173 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.psxvideo.bitstreams; + +import jpsxdec.psxvideo.mdec.MdecCode; + + +public class ZeroRunLengthAcLookup_STR { + + public static final ZeroRunLengthAc ESCAPE_CODE = new ZeroRunLengthAc(BitStreamCode._000001___________, true, false); + public static final ZeroRunLengthAc END_OF_BLOCK = new ZeroRunLengthAc(BitStreamCode._10_______________, + MdecCode.MDEC_END_OF_DATA_TOP6, MdecCode.MDEC_END_OF_DATA_BOTTOM10, false, true); + + /** The standard bit-stream Huffman variable length codes used in almost + * all PlayStation games. Conveniently identical to MPEG1. */ + public static final ZeroRunLengthAcLookup AC_VARIABLE_LENGTH_CODES_MPEG1 = new ZeroRunLengthAcLookup.Builder() + // Code "Run" "Level" + ._11s (0 , 1) + ._011s (1 , 1) + ._0100s (0 , 2) + ._0101s (2 , 1) + ._00101s (0 , 3) + ._00110s (4 , 1) + ._00111s (3 , 1) + ._000100s (7 , 1) + ._000101s (6 , 1) + ._000110s (1 , 2) + ._000111s (5 , 1) + ._0000100s (2 , 2) + ._0000101s (9 , 1) + ._0000110s (0 , 4) + ._0000111s (8 , 1) + ._00100000s (13, 1) + ._00100001s (0 , 6) + ._00100010s (12, 1) + ._00100011s (11, 1) + ._00100100s (3 , 2) + ._00100101s (1 , 3) + ._00100110s (0 , 5) + ._00100111s (10, 1) + ._0000001000s (16, 1) + ._0000001001s (5 , 2) + ._0000001010s (0 , 7) + ._0000001011s (2 , 3) + ._0000001100s (1 , 4) + ._0000001101s (15, 1) + ._0000001110s (14, 1) + ._0000001111s (4 , 2) + ._000000010000s (0 , 11) + ._000000010001s (8 , 2) + ._000000010010s (4 , 3) + ._000000010011s (0 , 10) + ._000000010100s (2 , 4) + ._000000010101s (7 , 2) + ._000000010110s (21, 1) + ._000000010111s (20, 1) + ._000000011000s (0 , 9) + ._000000011001s (19, 1) + ._000000011010s (18, 1) + ._000000011011s (1 , 5) + ._000000011100s (3 , 3) + ._000000011101s (0 , 8) + ._000000011110s (6 , 2) + ._000000011111s (17, 1) + ._0000000010000s (10, 2) + ._0000000010001s (9 , 2) + ._0000000010010s (5 , 3) + ._0000000010011s (3 , 4) + ._0000000010100s (2 , 5) + ._0000000010101s (1 , 7) + ._0000000010110s (1 , 6) + ._0000000010111s (0 , 15) + ._0000000011000s (0 , 14) + ._0000000011001s (0 , 13) + ._0000000011010s (0 , 12) + ._0000000011011s (26, 1) + ._0000000011100s (25, 1) + ._0000000011101s (24, 1) + ._0000000011110s (23, 1) + ._0000000011111s (22, 1) + ._00000000010000s (0 , 31) + ._00000000010001s (0 , 30) + ._00000000010010s (0 , 29) + ._00000000010011s (0 , 28) + ._00000000010100s (0 , 27) + ._00000000010101s (0 , 26) + ._00000000010110s (0 , 25) + ._00000000010111s (0 , 24) + ._00000000011000s (0 , 23) + ._00000000011001s (0 , 22) + ._00000000011010s (0 , 21) + ._00000000011011s (0 , 20) + ._00000000011100s (0 , 19) + ._00000000011101s (0 , 18) + ._00000000011110s (0 , 17) + ._00000000011111s (0 , 16) + ._000000000010000s (0 , 40) + ._000000000010001s (0 , 39) + ._000000000010010s (0 , 38) + ._000000000010011s (0 , 37) + ._000000000010100s (0 , 36) + ._000000000010101s (0 , 35) + ._000000000010110s (0 , 34) + ._000000000010111s (0 , 33) + ._000000000011000s (0 , 32) + ._000000000011001s (1 , 14) + ._000000000011010s (1 , 13) + ._000000000011011s (1 , 12) + ._000000000011100s (1 , 11) + ._000000000011101s (1 , 10) + ._000000000011110s (1 , 9) + ._000000000011111s (1 , 8) + ._0000000000010000s(1 , 18) + ._0000000000010001s(1 , 17) + ._0000000000010010s(1 , 16) + ._0000000000010011s(1 , 15) + ._0000000000010100s(6 , 3) + ._0000000000010101s(16, 2) + ._0000000000010110s(15, 2) + ._0000000000010111s(14, 2) + ._0000000000011000s(13, 2) + ._0000000000011001s(12, 2) + ._0000000000011010s(11, 2) + ._0000000000011011s(31, 1) + ._0000000000011100s(30, 1) + ._0000000000011101s(29, 1) + ._0000000000011110s(28, 1) + ._0000000000011111s(27, 1) + .add(ESCAPE_CODE) + .add(END_OF_BLOCK) + .build(); + + /** Debug */ + public static void main(String[] args) { + AC_VARIABLE_LENGTH_CODES_MPEG1.printAllLookupTables(System.out); + } + +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/package.html b/jpsxdec/src/jpsxdec/psxvideo/bitstreams/package.html deleted file mode 100644 index cbcc9b4..0000000 --- a/jpsxdec/src/jpsxdec/psxvideo/bitstreams/package.html +++ /dev/null @@ -1,32 +0,0 @@ - - -jPSXdec - -Compressed PlayStation 1 bitstream video data compression and decompression. -

        - The PlayStation 1 uses a video format similar to MPEG1 I-frames. - The final step in the encoding process compresses the data into - a stream of bits (a form of 'entropy' encoding). -

        -

        - There are 5 known bitstream formats: -

        -
          -
        • {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2}: - Sony developed version 2. - Used in the majority of games.
        • -
        • {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv1} - is nearly identical to STRv2, but has a couple interesting - variations worth identifying.
        • -
        • {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv3}: - Sony developed version 3. - Not used very much, but provides better compression, probably - at the cost of additional processing.
        • -
        • {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_Iki}: - .iki videos used by a few games.
        • -
        • {@link jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_Lain}: - Used only by the Serial Experiments Lain game
        • -
        - - - \ No newline at end of file diff --git a/jpsxdec/src/jpsxdec/psxvideo/encode/MacroBlockEncoder.java b/jpsxdec/src/jpsxdec/psxvideo/encode/MacroBlockEncoder.java index abfca05..dfcfaf5 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/encode/MacroBlockEncoder.java +++ b/jpsxdec/src/jpsxdec/psxvideo/encode/MacroBlockEncoder.java @@ -39,26 +39,35 @@ import java.util.ArrayList; import java.util.Iterator; +import javax.annotation.Nonnull; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import static jpsxdec.psxvideo.mdec.MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST; import jpsxdec.psxvideo.mdec.idct.StephensIDCT; /** Encodes a single macroblock into MDEC codes. */ -public class MacroBlockEncoder implements Iterable { +public class MacroBlockEncoder implements Iterable { private static final boolean DEBUG = false; private static final int[] PSX_DEFAULT_QUANTIZATION_MATRIX = - MdecInputStream.getDefaultPsxQuantMatrixCopy(); + new int[MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX.length]; + static { + System.arraycopy(MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX, 0, + PSX_DEFAULT_QUANTIZATION_MATRIX, 0, + MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX.length); + } // TODO: Change to use a Forward DCT more closely resembling the PSX private final StephensIDCT _DCT = new StephensIDCT(); private final double[][] _aadblYBlockVectors = new double[4][]; + @Nonnull private final double[] _adblCbBlockVector; + @Nonnull private final double[] _adblCrBlockVector; + @Nonnull private int[] _aiQscales, _aiSquashQscales; public final int X, Y; @@ -67,7 +76,7 @@ public class MacroBlockEncoder implements Iterable { * It is calculated using my best guess as an approach. */ private double _dblEnergy = 0; - MacroBlockEncoder(PsxYCbCrImage ycbcr, int iMacroBlockX, int iMacroBlockY) { + MacroBlockEncoder(@Nonnull PsxYCbCrImage ycbcr, int iMacroBlockX, int iMacroBlockY) { X = iMacroBlockX; Y = iMacroBlockY; @@ -96,7 +105,7 @@ public class MacroBlockEncoder implements Iterable { } - private double[] preEncodeBlock(double[] adblBlock, int iBlock) { + private @Nonnull double[] preEncodeBlock(@Nonnull double[] adblBlock, int iBlock) { if (DEBUG) { System.out.println("Pre DCT"); for (int y = 0; y < 8; y++) { @@ -139,7 +148,7 @@ private double[] preEncodeBlock(double[] adblBlock, int iBlock) { } - private double[] preQuantizeZigZagBlock(double[] adblBlock, int iBlock) { + private @Nonnull double[] preQuantizeZigZagBlock(@Nonnull double[] adblBlock, int iBlock) { double[] adblVector = new double[8*8]; // partially quantize it adblVector[0] = (int)Math.round(adblBlock[0] @@ -165,22 +174,22 @@ public double getEnergy() { return _dblEnergy; } - public void setToFullEncode(int[] aiQscales) { + public void setToFullEncode(@Nonnull int[] aiQscales) { if (aiQscales.length != 6) throw new IllegalArgumentException(); _aiSquashQscales = _aiQscales = aiQscales.clone(); } - public void setToPartialEncode(int[] aiQscales, int[] aiSquashQscales) { + public void setToPartialEncode(@Nonnull int[] aiQscales, @Nonnull int[] aiSquashQscales) { if (aiQscales.length != 6) throw new IllegalArgumentException(); _aiQscales = aiQscales.clone(); _aiSquashQscales = aiSquashQscales.clone(); } - public Iterator iterator() { + public @Nonnull Iterator iterator() { if (_aiQscales == null || _aiSquashQscales == null) throw new IllegalStateException(); - ArrayList codes = new ArrayList(); + ArrayList codes = new ArrayList(); encodeBlock(_adblCrBlockVector, codes, _aiQscales[0], _aiSquashQscales[0]); encodeBlock(_adblCbBlockVector, codes, _aiQscales[1], _aiSquashQscales[1]); encodeBlock(_aadblYBlockVectors[0], codes, _aiQscales[2], _aiSquashQscales[2]); @@ -192,11 +201,11 @@ public Iterator iterator() { // ------------------------------------------------------------------------- - private void encodeBlock(double[] adblVector, - ArrayList out, + private void encodeBlock(@Nonnull double[] adblVector, + @Nonnull ArrayList out, int iQscale, int iSquashQscale) { - final MdecInputStream.MdecCode code = new MdecInputStream.MdecCode(); + final MdecCode code = new MdecCode(); code.setTop6Bits(iQscale); code.setBottom10Bits((int)Math.round(adblVector[0])); out.add(code.copy()); diff --git a/jpsxdec/src/jpsxdec/psxvideo/encode/MdecEncoder.java b/jpsxdec/src/jpsxdec/psxvideo/encode/MdecEncoder.java index fde1c58..fd07a0f 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/encode/MdecEncoder.java +++ b/jpsxdec/src/jpsxdec/psxvideo/encode/MdecEncoder.java @@ -43,9 +43,9 @@ import java.util.List; import javax.annotation.Nonnull; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; /** Encodes a {@link PsxYCbCrImage} into an {@link MdecInputStream}. * After encoding, the MdecInputStream will most likely be then @@ -59,6 +59,7 @@ public class MdecEncoder implements Iterable { private final int _iMacBlockHeight; /** Used for full frame replace. */ + @SuppressWarnings("unchecked") public MdecEncoder(@Nonnull PsxYCbCrImage ycbcr, int iWidth, int iHeight) { if (ycbcr.getLumaWidth() % 16 != 0 || ycbcr.getLumaHeight() % 16 != 0) @@ -82,9 +83,10 @@ public MdecEncoder(@Nonnull PsxYCbCrImage ycbcr, int iWidth, int iHeight) { } /** Used for partial replace. */ + @SuppressWarnings("unchecked") public MdecEncoder(@Nonnull ParsedMdecImage original, @Nonnull PsxYCbCrImage newYcbcr, - @Nonnull List replaceMbs) + @Nonnull List macroBlocksToReplace) { if (newYcbcr.getLumaWidth() % 16 != 0 || newYcbcr.getLumaHeight() % 16 != 0) @@ -102,7 +104,7 @@ public MdecEncoder(@Nonnull ParsedMdecImage original, for (int iMbY = 0; iMbY < _iMacBlockHeight; iMbY++) { p.setLocation(iMbX, iMbY); Iterable enc; - if (replaceMbs.contains(p)) { + if (macroBlocksToReplace.contains(p)) { MacroBlockEncoder e = new MacroBlockEncoder(newYcbcr, iMbX, iMbY); _replaceMbs.add(e); enc = e; @@ -144,7 +146,7 @@ public int getPixelHeight() { return _iPixHeight; } - private class EncodedMdecInputStream extends MdecInputStream { + private class EncodedMdecInputStream implements MdecInputStream { private int __iCurMacBlk = 0; private Iterator __curMb; @@ -161,7 +163,7 @@ public boolean readMdecCode(@Nonnull MdecCode code) throws MdecException.EndOfSt __iCurMacBlk++; __curMb = _aoMacroBlocks[__iCurMacBlk].iterator(); } - code.set(__curMb.next()); + code.setFrom(__curMb.next()); return code.isEOD(); // hopefully no bad EOD codes are part of the list } diff --git a/jpsxdec/src/jpsxdec/psxvideo/encode/ParsedMdecImage.java b/jpsxdec/src/jpsxdec/psxvideo/encode/ParsedMdecImage.java index e59ceef..64f9653 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/encode/ParsedMdecImage.java +++ b/jpsxdec/src/jpsxdec/psxvideo/encode/ParsedMdecImage.java @@ -46,9 +46,10 @@ import javax.annotation.Nonnull; import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; /** Parses and stores a stream of MDEC 16-bit codes in a structure for easier analysis. */ public class ParsedMdecImage { @@ -56,13 +57,13 @@ public class ParsedMdecImage { private static final Logger LOG = Logger.getLogger(ParsedMdecImage.class.getName()); private static class MacroBlock implements Iterable { - public final Block[] _aoBlocks = new Block[6]; + public final Block[] _aoBlocks = new Block[MdecBlock.count()]; public MacroBlock(@Nonnull MdecInputStream mdecIn) throws MdecException.EndOfStream, MdecException.ReadCorruption { - for (int iBlock = 0; iBlock < 6; iBlock++) { - _aoBlocks[iBlock] = new Block(iBlock, mdecIn); + for (MdecBlock block : MdecBlock.list()) { + _aoBlocks[block.ordinal()] = new Block(block, mdecIn); } } @@ -92,7 +93,7 @@ public ArrayList getCodes() { private int _iBlk = 0; public boolean hasNext() { - return _iBlk < 6; + return _iBlk < MdecBlock.count(); } public @Nonnull Block next() { @@ -110,22 +111,17 @@ public void remove() { private static class Block { - private static final String[] BLOCK_NAMES = { - "Cr", "Cb", "Y1", "Y2", "Y3", "Y4" - }; - - private final int _iIndex; + @Nonnull + private final MdecBlock _block; @Nonnull private final MdecCode[] _aoCodes; /** Only available if source data was a bitstream. -1 if unknown. */ private final int _iBitSize; - public Block(int iIndex, @Nonnull MdecInputStream mdecIn) + public Block(@Nonnull MdecBlock block, @Nonnull MdecInputStream mdecIn) throws MdecException.EndOfStream, MdecException.ReadCorruption { - if (iIndex < 0 || iIndex >= 6) - throw new IllegalArgumentException("Invalid block index " + iIndex); - _iIndex = iIndex; + _block = block; ArrayList codes = new ArrayList(); int iBitStart = -1; @@ -147,10 +143,6 @@ public Block(int iIndex, @Nonnull MdecInputStream mdecIn) _iBitSize = ((BitStreamUncompressor)mdecIn).getBitPosition() - iBitStart; } - public @Nonnull String getName() { - return BLOCK_NAMES[_iIndex]; - } - public @Nonnull MdecCode getMdecCode(int i) { return _aoCodes[i]; } @@ -159,18 +151,6 @@ public int getMdecCodeCount() { return _aoCodes.length; } - public boolean isChroma() { - return _iIndex < 2; - } - - public boolean isLuma() { - return _iIndex >= 2; - } - - public int getIndex() { - return _iIndex; - } - public int getQscale() { return _aoCodes[0].getTop6Bits(); } @@ -190,7 +170,7 @@ public int getBitSize() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(getName()).append(" Q:").append(getQscale()); + sb.append(_block.name()).append(" Q:").append(getQscale()); if (_iBitSize != -1) sb.append(" Size=").append(_iBitSize); return sb.toString(); @@ -199,7 +179,7 @@ public String toString() { } - private class MdecReader extends MdecInputStream { + private class MdecReader implements MdecInputStream { // TODO: convert to use iterator? private int __iCurrentMacroBlock; @@ -222,7 +202,7 @@ public boolean readMdecCode(@Nonnull MdecCode code) throws MdecException.EndOfSt Block currentBlk = currentMacBlk.getBlock(__iCurrentBlock); MdecCode c = currentBlk.getMdecCode(__iCurrentMdecCode); - code.set(c); + code.setFrom(c); __iCurrentMdecCode++; diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Cleaner.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Checker.java similarity index 58% rename from jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Cleaner.java rename to jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Checker.java index 12fea8e..10c964e 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Cleaner.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/Ac0Checker.java @@ -1,97 +1,124 @@ -/* - * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2015-2019 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.psxvideo.mdec; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** Filters out MDEC codes where AC=0. - * Known to happen in FF7 and Judge Dredd videos, but could happen anywhere. - * MDEC codes where AC=0 are redundant and just waste space. - * This will merge those codes with adjacent codes by extending zero-run-length. - */ -public class Ac0Cleaner extends MdecInputStream { - - private static final Logger LOG = Logger.getLogger(Ac0Cleaner.class.getName()); - - private final MdecInputStream _source; - private boolean _blnNextCodeIsQscaleDC = true; - - public Ac0Cleaner(MdecInputStream source) { - _source = source; - } - - @Override - public boolean readMdecCode(MdecCode code) - throws MdecException.EndOfStream, MdecException.ReadCorruption - { - boolean blnEod = _source.readMdecCode(code); - - if (_blnNextCodeIsQscaleDC) { - _blnNextCodeIsQscaleDC = false; - if (blnEod) // there's something very strange going on if this happens - LOG.log(Level.WARNING, "(qscale,DC) code says is EOD"); - return blnEod; - } - - if (blnEod) { - _blnNextCodeIsQscaleDC = true; - return true; - } - - if (code.getBottom10Bits() == 0) { - LOG.log(Level.INFO, "Found AC=0 code {0}", code); - final MdecCode nextCode = new MdecCode(); - do { - blnEod = _source.readMdecCode(nextCode); - if (blnEod) { - LOG.log(Level.INFO, "Discarding merge {0} at EOD", code); - code.set(nextCode); - _blnNextCodeIsQscaleDC = true; - return true; - } - code.setTop6Bits(code.getTop6Bits() + nextCode.getTop6Bits() + 1); - code.setBottom10Bits(nextCode.getBottom10Bits()); - LOG.log(Level.INFO, "Merged code {0} into prev AC=0 code to form {1}", - new Object[] {nextCode, code}); - } while (code.getBottom10Bits() == 0); - } - return false; - } - -} +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2015-2019 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.psxvideo.mdec; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nonnull; + +/** Filters out MDEC codes where AC=0. + * Known to happen in FF7 and Judge Dredd videos, but could happen anywhere. + * MDEC codes where AC=0 are redundant and just waste space. + * This will merge those codes with adjacent codes by extending zero-run-length. + */ +public class Ac0Checker implements MdecInputStream { + + private static final Logger LOG = Logger.getLogger(Ac0Checker.class.getName()); + + public static Ac0Checker wrapWithChecker(@Nonnull MdecInputStream source, boolean blnAlsoClean) { + if (source instanceof Ac0Checker) + return (Ac0Checker)source; + else + return new Ac0Checker(source, blnAlsoClean); + } + + private final MdecInputStream _source; + private final boolean _blnAlsoClean; + + private boolean _blnNextCodeIsQscaleDC = true; + private int _iEscapeCodeAc0Count = 0; + + public Ac0Checker(MdecInputStream source, boolean blnAlsoClean) { + _source = source; + _blnAlsoClean = blnAlsoClean; + } + + final public int get0AcCoefficientCount() { + return _iEscapeCodeAc0Count; + } + + final public void logIfAny0AcCoefficient() { + if (_iEscapeCodeAc0Count > 0) { + LOG.log(Level.INFO, "Frame had {0} codes 0 AC coefficient", _iEscapeCodeAc0Count); + } + } + + @Override + public boolean readMdecCode(MdecCode code) + throws MdecException.EndOfStream, MdecException.ReadCorruption + { + boolean blnEod = _source.readMdecCode(code); + + if (_blnNextCodeIsQscaleDC) { + _blnNextCodeIsQscaleDC = false; + if (blnEod) // there's something very strange going on if this happens + LOG.log(Level.WARNING, "(qscale,DC) code says it's EOD"); + } else if (blnEod) { + _blnNextCodeIsQscaleDC = true; + } else if (code.getBottom10Bits() == 0) { + _iEscapeCodeAc0Count++; + LOG.log(Level.FINE, "Found AC=0 code {0}", code); + if (_blnAlsoClean) + blnEod = filter0(code); + } + return blnEod; + } + + private boolean filter0(MdecCode code) + throws MdecException.EndOfStream, MdecException.ReadCorruption + { + boolean blnEod; + final MdecCode nextCode = new MdecCode(); + do { + blnEod = _source.readMdecCode(nextCode); + if (blnEod) { + LOG.log(Level.FINE, "Discarding merge {0} at EOD", code); + code.setFrom(nextCode); + _blnNextCodeIsQscaleDC = true; + break; + } + code.setTop6Bits(code.getTop6Bits() + nextCode.getTop6Bits() + 1); + code.setBottom10Bits(nextCode.getBottom10Bits()); + LOG.log(Level.FINE, "Merged code {0} into prev AC=0 code to form {1}", + new Object[] {nextCode, code}); + } while (code.getBottom10Bits() == 0); + + return blnEod; + } + +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/Calc.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/Calc.java index bf6d181..2fc8dae 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/Calc.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/Calc.java @@ -42,7 +42,7 @@ public class Calc { /** Number of blocks necessary to store pixel size. */ public static int blocks(int iPixelWidth, int iPixelHeight) { - return macroblocks(iPixelWidth, iPixelHeight) * 6; + return macroblocks(iPixelWidth, iPixelHeight) * MdecBlock.count(); } /** Number of macroblocks necessary to store pixel size. */ public static int macroblocks(int iPixelWidth, int iPixelHeight) { @@ -59,4 +59,12 @@ public static int macroblockDim(int iPixelDimension) { return (iPixelDimension + 15) / 16; } + /** A strange value needed for video bitstreams and video sector headers. + * It's the number of MDEC codes, divided by two, then rounded up to the + * next closest multiple of 32 (if not already a multiple of 32). + * In other words, its the number of 32-byte blocks it would take to hold + * the MDEC codes. */ + public static short calculateHalfCeiling32(int iMdecCodeCount) { + return (short) ((((iMdecCodeCount + 1) / 2) + 31) & ~31); + } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/ChromaUpsample.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/ChromaUpsample.java new file mode 100644 index 0000000..41c77e9 --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/ChromaUpsample.java @@ -0,0 +1,122 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.psxvideo.mdec; + +import com.mortennobel.imagescaling.ResampleFilter; +import com.mortennobel.imagescaling.ResampleFilters; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.i18n.I; +import jpsxdec.i18n.ILocalizedMessage; + +public enum ChromaUpsample { + /** i.e. Box */ + NearestNeighbor(I.CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION(), + I.CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE(), null), + + /** i.e. Triangle */ + Bilinear(I.CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION(), + I.CHROMA_UPSAMPLE_BILINEAR_CMDLINE(), null), + + Bicubic(I.CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION(), + I.CHROMA_UPSAMPLE_BICUBIC_CMDLINE(), ResampleFilters.getBiCubicFilter()), + + Bell(I.CHROMA_UPSAMPLE_BELL_DESCRIPTION(), + I.CHROMA_UPSAMPLE_BELL_CMDLINE(), ResampleFilters.getBellFilter()), + + Mitchell(I.CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION(), + I.CHROMA_UPSAMPLE_MITCHELL_CMDLINE(), ResampleFilters.getMitchellFilter()), + + BSpline(I.CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION(), + I.CHROMA_UPSAMPLE_BSPLINE_CMDLINE(), ResampleFilters.getBSplineFilter()), + + Lanczos3(I.CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION(), + I.CHROMA_UPSAMPLE_LANCZOS3_CMDLINE(), ResampleFilters.getLanczos3Filter()), + + Hermite(I.CHROMA_UPSAMPLE_HERMITE_DESCRIPTION(), + I.CHROMA_UPSAMPLE_HERMITE_CMDLINE(), ResampleFilters.getHermiteFilter()); + + @Nonnull + private final ILocalizedMessage _description; + private final ILocalizedMessage _cmdLine; + @CheckForNull + final ResampleFilter _filter; + + private ChromaUpsample(@Nonnull ILocalizedMessage description, + @Nonnull ILocalizedMessage cmdLine, + @CheckForNull ResampleFilter filter) + { + _description = description; + _cmdLine = cmdLine; + _filter = filter; + } + + public static ChromaUpsample fromCmdLine(String sCmdLine) { + ChromaUpsample up = null; + for (ChromaUpsample upsampler : values()) { + if (upsampler.getCmdLine().equalsIgnoreCase(sCmdLine)) { + up = upsampler; + break; + } + } + return up; + } + + public ILocalizedMessage getDescription() { + return _description; + } + + public ILocalizedMessage getCmdLine() { + return _cmdLine; + } + + public ILocalizedMessage getCmdLineHelp() { + if (_cmdLine.equals(_description)) + return _cmdLine; + else + return I.CHROMA_UPSAMPLE_CMDLINE_HELP(_cmdLine, _description); + } + + /** {@inheritDoc} + *

        + * Used in GUI list so must be localized. */ + @Override + public String toString() { + return getDescription().getLocalizedMessage(); + } +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecBlock.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecBlock.java new file mode 100644 index 0000000..43c0e6d --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecBlock.java @@ -0,0 +1,100 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.psxvideo.mdec; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + + +/** Macro-block sub blocks, listed in the order accepted by the MDEC chip. */ +public enum MdecBlock { + Cr(true), + Cb(true), + Y1(false), + Y2(false), + Y3(false), + Y4(false); + + private final boolean _blnIsChroma; + private MdecBlock _nextBlock; + + private MdecBlock(boolean blnIsChroma) { + _blnIsChroma = blnIsChroma; + } + + public boolean isChroma() { + return _blnIsChroma; + } + + public boolean isLuma() { + return !_blnIsChroma; + } + + /** Never returns null, loops from the last block to the first. */ + public @Nonnull MdecBlock next() { + return _nextBlock; + } + + // ------------------------------------------------------------------------- + + private static final List VALUES = + Collections.unmodifiableList(Arrays.asList(values())); + + static { + Cr._nextBlock = Cb; + Cb._nextBlock = Y1; + Y1._nextBlock = Y2; + Y2._nextBlock = Y3; + Y3._nextBlock = Y4; + Y4._nextBlock = Cr; + } + + public static @Nonnull List list() { + return VALUES; + } + + public static int count() { + return VALUES.size(); + } + + public static @Nonnull MdecBlock first() { + return Cr; + } +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecCode.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecCode.java new file mode 100644 index 0000000..b3f003e --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecCode.java @@ -0,0 +1,215 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2007-2019 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.psxvideo.mdec; + +import javax.annotation.Nonnull; +import jpsxdec.util.Misc; + + +/** Represents a 16-bit code readable by the PlayStation MDEC chip. + * If the MDEC code is the first of a block, the top 6 bits indicate + * the block's quantization scale, and the bottom 10 bits indicate + * the "direct current" (DC) coefficient. + * If the MDEC code is not the first of a block, and it is + * not a {@link #MDEC_END_OF_DATA} code (0xFE00), then the top 6 bits indicate + * the number of zeros preceeding an "alternating current" (AC) coefficient, + * with the bottom 10 bits indicating a (usually) non-zero AC coefficient. */ +public class MdecCode implements Comparable { + + /** 16-bit MDEC code indicating the end of a block. + * The equivalent MDEC value is (63, -512). */ + public final static int MDEC_END_OF_DATA = 0xFE00; + /** Top 6 bits of {@link #MDEC_END_OF_DATA}. */ + public final static int MDEC_END_OF_DATA_TOP6 = (MDEC_END_OF_DATA >> 10) & 63; + /** Bottom 10 bits of {@link #MDEC_END_OF_DATA}. */ + public final static int MDEC_END_OF_DATA_BOTTOM10 = (short)(MDEC_END_OF_DATA | 0xFC00); + + /** Most significant 6 bits of the 16-bit MDEC code. + * Holds either a block's quantization scale or the + * count of zero AC coefficients leading up to a non-zero + * AC coefficient. */ + private int _iTop6Bits; + + /** Least significant 10 bits of the 16-bit MDEC code. + * Holds either the DC coefficient of a block or + * a non-zero AC coefficient. */ + private int _iBottom10Bits; + + /** Initializes to (0, 0). */ + public MdecCode() { + _iTop6Bits = 0; + _iBottom10Bits = 0; + } + + public MdecCode(int iTop6Bits, int iBottom10Bits) { + if (!validTop(iTop6Bits)) + throw new IllegalArgumentException("Invalid top 6 bits " + iTop6Bits); + if (!validBottom(iBottom10Bits)) + throw new IllegalArgumentException("Invalid bottom 10 bits " + iBottom10Bits); + _iTop6Bits = iTop6Bits; + _iBottom10Bits = iBottom10Bits; + } + + /** Extract the top 6 bit and bottom 10 bit values from 16 bits */ + public MdecCode(int iMdecWord) { + set(iMdecWord); + } + + public void setFrom(@Nonnull MdecCode other) { + _iTop6Bits = other._iTop6Bits; + _iBottom10Bits = other._iBottom10Bits; + } + + public int getBottom10Bits() { + return _iBottom10Bits; + } + + /** By default does not verify the value for performance. */ + public void setBottom10Bits(int iBottom10Bits) { + assert validBottom(iBottom10Bits); + _iBottom10Bits = iBottom10Bits; + } + + public int getTop6Bits() { + return _iTop6Bits; + } + + /** By default does not verify the value for performance. */ + public void setTop6Bits(int iTop6Bits) { + assert validTop(iTop6Bits); + _iTop6Bits = iTop6Bits; + } + + /** By default does not verify the values for performance. */ + public void setBits(int iTop6, int iBottom10) { + assert validTop(_iTop6Bits) && validBottom(_iBottom10Bits); + _iTop6Bits = iTop6; + _iBottom10Bits = iBottom10; + } + + public void set(int iMdecWord) { + _iTop6Bits = ((iMdecWord >> 10) & 63); + _iBottom10Bits = (iMdecWord & 0x3FF); + if ((_iBottom10Bits & 0x200) == 0x200) { // is it negitive? + _iBottom10Bits -= 0x400; + } + } + + /** Combines the top 6 bits and bottom 10 bits into an unsigned 16 bit value. */ + public int toMdecWord() { + if (isEOD()) + return MDEC_END_OF_DATA; + if (!validTop(_iTop6Bits)) + throw new IllegalStateException("MDEC code has invalid top 6 bits " + _iTop6Bits); + if (!validBottom(_iBottom10Bits)) + throw new IllegalStateException("MDEC code has invalid bottom 10 bits " + _iBottom10Bits); + return ((_iTop6Bits & 63) << 10) | (_iBottom10Bits & 0x3FF); + } + + /** Set the MDEC code to the special "End of Data" (EOD) value, + * indicating the end of a block. + * @see MdecInputStream#MDEC_END_OF_DATA */ + public @Nonnull MdecCode setToEndOfData() { + _iTop6Bits = MDEC_END_OF_DATA_TOP6; + _iBottom10Bits = MDEC_END_OF_DATA_BOTTOM10; + return this; + } + + /** Returns if this MDEC code is setFrom to the special "End of Data" (EOD) + value. + * @see MdecInputStream#MDEC_END_OF_DATA */ + public boolean isEOD() { + return (_iTop6Bits == MDEC_END_OF_DATA_TOP6 && + _iBottom10Bits == MDEC_END_OF_DATA_BOTTOM10); + } + + /** Returns if this MDEC code has valid values. + * As an optimization, many parameter checks are disabled, so + * this MDEC code could hold values that are be invalid. */ + public boolean isValid() { + return validTop(_iTop6Bits) && validBottom(_iBottom10Bits); + } + + /** Checks if the top 6 bits of an MDEC code are valid. */ + private static boolean validTop(int iTop6Bits) { + return iTop6Bits >= 0 && iTop6Bits <= 63; + } + /** Checks if the bottom 10 bits of an MDEC code are valid. */ + private static boolean validBottom(int iBottom10Bits) { + return iBottom10Bits >= -512 && iBottom10Bits <= 511; + } + + public @Nonnull MdecCode copy() { + return new MdecCode(_iTop6Bits, _iBottom10Bits); + } + + @Override + public String toString() { + String s = String.format("%04x (%d, %d)", toMdecWord(), _iTop6Bits, _iBottom10Bits); + if (isEOD()) + return s + " EOD"; + else + return s; + } + + public int compareTo(MdecCode o) { + int i = Misc.intCompare(_iTop6Bits, o._iTop6Bits); + if (i != 0) + return i; + return Misc.intCompare(_iBottom10Bits, o._iBottom10Bits); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final MdecCode other = (MdecCode) obj; + return _iTop6Bits == other._iTop6Bits && + _iBottom10Bits == other._iBottom10Bits; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + _iTop6Bits; + hash = 97 * hash + _iBottom10Bits; + return hash; + } + +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecContext.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecContext.java new file mode 100644 index 0000000..3685c96 --- /dev/null +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecContext.java @@ -0,0 +1,164 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.psxvideo.mdec; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** Tracks MDEC codes, blocks, macro-blocks, and coordinates during + * the decoding process. One-time use. */ +public class MdecContext { + + private static final Logger LOG = Logger.getLogger(MdecContext.class.getName()); + + private int _iCurrentMacroBlock = 0; + @Nonnull + private MdecBlock _currentBlock = MdecBlock.first(); + private int _iCurrentMdecCodeInCurrentBlock = 0; + + private int _iCurrentTotalBlocks = 0; + private int _iCurrentTotalMdecCode = 0; + + @CheckForNull + private final Integer _oiMacroBlockHeight; + + /** By providing the height of the frame, it allows tracking of what pixel + * x/y is being decoded. */ + public MdecContext(int iFrameMacroBlockHeight) { + _oiMacroBlockHeight = Integer.valueOf(iFrameMacroBlockHeight); + } + + public MdecContext() { + _oiMacroBlockHeight = null; + } + + public int getTotalMacroBlocksRead() { + return _iCurrentMacroBlock; + } + + /** Total number of sub-blocks that have been read thus far. */ + public int getTotalBlocksRead() { + return _iCurrentTotalBlocks; + } + + /** Total number of MDEC codes that have been read thus far. */ + public int getTotalMdecCodesRead() { + return _iCurrentTotalMdecCode; + } + + public @Nonnull MdecBlock getCurrentBlock() { + return _currentBlock; + } + + public int getMdecCodesReadInCurrentBlock() { + return _iCurrentMdecCodeInCurrentBlock; + } + + /** Indicates if the next read should be the Quantization Scale and DC Coefficient. */ + public boolean atStartOfBlock() { + return _iCurrentMdecCodeInCurrentBlock == 0; + } + + /** Increments the number of MDEC codes that have been read. */ + public void nextCode() { + _iCurrentTotalMdecCode++; + _iCurrentMdecCodeInCurrentBlock++; + if (_iCurrentMdecCodeInCurrentBlock > 64) + LOG.log(Level.WARNING, "Impossible number of codes in a block {0}", _iCurrentMdecCodeInCurrentBlock); + } + + /** Increments the number of MDEC codes that have been read and end the block. */ + public void nextCodeEndBlock() { + _iCurrentTotalMdecCode++; + _iCurrentMdecCodeInCurrentBlock = 0; + _iCurrentTotalBlocks++; + _currentBlock = _currentBlock.next(); + if (_currentBlock == MdecBlock.first()) + _iCurrentMacroBlock++; + } + + public @Nonnull MdecContext copy() { + MdecContext c = new MdecContext(); + c._iCurrentMacroBlock = _iCurrentMacroBlock; + c._currentBlock = _currentBlock; + c._iCurrentMdecCodeInCurrentBlock = _iCurrentMdecCodeInCurrentBlock; + c._iCurrentTotalBlocks = _iCurrentTotalBlocks; + c._iCurrentTotalMdecCode = _iCurrentTotalMdecCode; + return c; + } + + /** The pixel of the top-left corner of a macro-block. */ + public static class MacroBlockPixel { + public final int x; + public final int y; + + private MacroBlockPixel(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } + } + + /** Returns null if no dimensions were specified in constructor. */ + public @CheckForNull MacroBlockPixel getMacroBlockPixel() { + if (_oiMacroBlockHeight == null) + return null; + int iMacroBlockHeight = _oiMacroBlockHeight.intValue(); + return new MacroBlockPixel((_iCurrentMacroBlock / iMacroBlockHeight) * 16, (_iCurrentMacroBlock % iMacroBlockHeight) * 16); + } + + @Override + public String toString() { + String s = String.format("Macro.block.code %d.%s(%d).%d total blocks %d codes %d", + _iCurrentMacroBlock, _currentBlock, _currentBlock.ordinal(), + _iCurrentMdecCodeInCurrentBlock, + _iCurrentTotalBlocks, + _iCurrentTotalMdecCode); + MacroBlockPixel pixel = getMacroBlockPixel(); + if (pixel == null) + return s; + else + return s + " " + pixel; + } +} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder.java index d0c32cc..7174372 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder.java @@ -38,25 +38,23 @@ package jpsxdec.psxvideo.mdec; import java.util.Arrays; +import javax.annotation.Nonnull; /** Super class of the two different MDEC decoders: int and double. */ public abstract class MdecDecoder { public static boolean DEBUG = false; - protected static final String[] BLOCK_NAMES = { - "Cr", "Cb", "Y1", "Y2", "Y3", "Y4" - }; - protected static boolean debugPrintln(String s) { System.out.println(s); return true; } - protected final MdecInputStream.MdecCode _code = new MdecInputStream.MdecCode(); + protected final MdecCode _code = new MdecCode(); - protected final int _iMacBlockWidth; + private final int _iMacBlockWidth; protected final int _iMacBlockHeight; + protected final int _iTotalMacBlocks; /** Luma dimensions. */ protected final int W, H; @@ -66,14 +64,19 @@ protected static boolean debugPrintln(String s) { protected final int[] _aiLumaBlkOfsLookup; protected final int[] _aiChromaMacBlkOfsLookup; - protected final int[] _aiQuantizationTable = - MdecInputStream.getDefaultPsxQuantMatrixCopy(); + protected final int[] _aiQuantizationTable = + new int[MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX.length]; protected final int[] _aiDebugPreqantBlock; protected MdecDecoder(int iWidth, int iHeight) { + System.arraycopy(MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX, 0, + _aiQuantizationTable, 0, + MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX.length); + _iMacBlockWidth = Calc.macroblockDim(iWidth); _iMacBlockHeight = Calc.macroblockDim(iHeight); + _iTotalMacBlocks = _iMacBlockWidth * _iMacBlockHeight; W = _iMacBlockWidth * 16; H = _iMacBlockHeight * 16; CW = _iMacBlockWidth * 8; @@ -100,10 +103,10 @@ protected MdecDecoder(int iWidth, int iHeight) { } } - boolean blnAssert = false; - assert blnAssert = true; + boolean blnAssertsEnabled = false; + assert blnAssertsEnabled = true; - if (blnAssert && DEBUG) + if (blnAssertsEnabled && DEBUG) _aiDebugPreqantBlock = new int[64]; else _aiDebugPreqantBlock = null; @@ -134,18 +137,18 @@ protected boolean debugPrintPrequantBlock() { /** Reads an image from the MdecInputStream and decodes it to an internal * PSX YCbCr buffer. */ - abstract public void decode(MdecInputStream mdecStream) + abstract public void decode(@Nonnull MdecInputStream mdecStream) throws MdecException.EndOfStream, MdecException.ReadCorruption; /** Retrieve the contents of the internal PSX YCbCr buffer converted to RGB. */ - abstract public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, + abstract public void readDecodedRgb(int iDestWidth, int iDestHeight, @Nonnull int[] aiDest, int iOutStart, int iOutStride); - public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest) { + public void readDecodedRgb(int iDestWidth, int iDestHeight, @Nonnull int[] aiDest) { readDecodedRgb(iDestWidth, iDestHeight, aiDest, 0, iDestWidth); } - public void setQuantizationTable(int[] aiNewTable) { + public void setQuantizationTable(@Nonnull int[] aiNewTable) { if (aiNewTable.length != _aiQuantizationTable.length) throw new IllegalArgumentException("Incorrect table size"); System.arraycopy(aiNewTable, 0, _aiQuantizationTable, 0, _aiQuantizationTable.length); diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double.java index da00a4b..6d6c9e5 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double.java @@ -37,7 +37,9 @@ package jpsxdec.psxvideo.mdec; +import com.mortennobel.imagescaling.ResampleOp; import java.util.Arrays; +import javax.annotation.Nonnull; import jpsxdec.formats.RGB; import jpsxdec.formats.Rec601YCbCr; import jpsxdec.formats.YCbCrImage; @@ -45,131 +47,156 @@ import jpsxdec.psxvideo.mdec.idct.IDCT_double; /** A full Java, double-precision, floating point implementation of the - * PlayStation 1 MDEC chip. This implementation also comes with additional - * methods for higher quality YUV decoding. */ + * PlayStation 1 MDEC chip with interpolation used in chroma upsampling. + *

        + * Default upsampling method is Bicubic. + *

        + * This implementation also comes with additional methods for higher + * quality YUV decoding. */ public class MdecDecoder_double extends MdecDecoder { - protected final IDCT_double _idct; + @Nonnull + private final IDCT_double _idct; - protected final double[] _CrBuffer; - protected final double[] _CbBuffer; - protected final double[] _LumaBuffer; + @Nonnull + private final double[] _adblDecodedCrBuffer; + @Nonnull + private final double[] _dblDecodedCbBuffer; + @Nonnull + private final double[] _adblDecodedLumaBuffer; /** Matrix of 8x8 coefficient values. */ - protected final double[] _CurrentBlock = new double[64]; + private final double[] _CurrentBlock = new double[64]; - public MdecDecoder_double(IDCT_double idct, int iWidth, int iHeight) { + /** Temp buffer for upsampled Cr. */ + @Nonnull + private final double[] _adblTempUpsampledCr; + /** Temp buffer for upsampled Cb. */ + @Nonnull + private final double[] _adblTempUpsampledCb; + + private final ResampleOp _resampler = new ResampleOp(); + @Nonnull + private ChromaUpsample _upsampler = ChromaUpsample.Bicubic; + + public MdecDecoder_double(@Nonnull IDCT_double idct, int iWidth, int iHeight) { super(iWidth, iHeight); _idct = idct; - - _CrBuffer = new double[ CW*CH]; - _CbBuffer = new double[ _CrBuffer.length]; - _LumaBuffer = new double[ W*H]; + + _adblDecodedCrBuffer = new double[CW * CH]; + _dblDecodedCbBuffer = new double[_adblDecodedCrBuffer.length]; + _adblDecodedLumaBuffer = new double[W * H]; + + _adblTempUpsampledCb = new double[_adblDecodedLumaBuffer.length]; + _adblTempUpsampledCr = new double[_adblDecodedLumaBuffer.length]; + + _resampler.setNumberOfThreads(1); + } + + public void setUpsampler(@Nonnull ChromaUpsample u) { + _upsampler = u; } - public void decode(MdecInputStream mdecInStream) + public void decode(@Nonnull MdecInputStream sourceMdecInStream) throws MdecException.EndOfStream, MdecException.ReadCorruption { + Ac0Checker mdecInStream = Ac0Checker.wrapWithChecker(sourceMdecInStream, false); int iCurrentBlockQscale; int iCurrentBlockVectorPosition; int iCurrentBlockNonZeroCount; int iCurrentBlockLastNonZeroPosition; - int iMacBlk = 0, iBlock = 0; + MdecContext context = new MdecContext(_iMacBlockHeight); try { // decode all the macro blocks of the image - for (int iMacBlkX = 0; iMacBlkX < _iMacBlockWidth; iMacBlkX ++) - { - for (int iMacBlkY = 0; iMacBlkY < _iMacBlockHeight; iMacBlkY ++) - { - // debug - assert !DEBUG || debugPrintln(String.format("############### Decoding macro block %d (%d, %d) ###############", - iMacBlk, iMacBlkX, iMacBlkY)); + while (context.getTotalMacroBlocksRead() < _iTotalMacBlocks) { + // debug + assert !DEBUG || debugPrintln(String.format("############### Decoding macro block %d %s ###############", + context.getTotalMacroBlocksRead(), context.getMacroBlockPixel())); - for (iBlock = 0; iBlock < 6; iBlock++) { + for (int iBlock = 0; iBlock < MdecBlock.count(); iBlock++) { - assert !DEBUG || debugPrintln(String.format("=========== Decoding block %s ===========", - BLOCK_NAMES[iBlock])); - - Arrays.fill(_CurrentBlock, 0); - mdecInStream.readMdecCode(_code); + assert !DEBUG || debugPrintln("=========== Decoding block "+context.getCurrentBlock()+" ==========="); - assert !DEBUG || debugPrintln("Qscale & DC " + _code); + Arrays.fill(_CurrentBlock, 0); + mdecInStream.readMdecCode(_code); - if (_code.getBottom10Bits() != 0) { - _CurrentBlock[0] = - _code.getBottom10Bits() * _aiQuantizationTable[0]; - iCurrentBlockNonZeroCount = 1; - iCurrentBlockLastNonZeroPosition = 0; - } else { - iCurrentBlockNonZeroCount = 0; - iCurrentBlockLastNonZeroPosition = -1; - } - assert !DEBUG || setPrequantValue(0, _code.getBottom10Bits()); - iCurrentBlockQscale = _code.getTop6Bits(); - iCurrentBlockVectorPosition = 0; - - while (!mdecInStream.readMdecCode(_code)) { - - assert !DEBUG || debugPrintln(_code.toString()); - - //////////////////////////////////////////////////////// - iCurrentBlockVectorPosition += _code.getTop6Bits() + 1; - - int iRevZigZagMatrixPos; - try { - // Reverse Zig-Zag - iRevZigZagMatrixPos = MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[iCurrentBlockVectorPosition]; - } catch (ArrayIndexOutOfBoundsException ex) { - throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_BLOCK_NAME( - iCurrentBlockVectorPosition, - iMacBlk, iMacBlkX, iMacBlkY, iBlock, BLOCK_NAMES[iBlock]), - ex); - } - - if (_code.getBottom10Bits() != 0) { - - assert !DEBUG || setPrequantValue(iRevZigZagMatrixPos, _code.getBottom10Bits()); - // Dequantize - _CurrentBlock[iRevZigZagMatrixPos] = - (_code.getBottom10Bits() - * _aiQuantizationTable[iRevZigZagMatrixPos] - * iCurrentBlockQscale) / 8.0; - iCurrentBlockNonZeroCount++; - iCurrentBlockLastNonZeroPosition = iRevZigZagMatrixPos; - - } - //////////////////////////////////////////////////////// - } + assert !DEBUG || debugPrintln("Qscale & DC " + _code); + + if (_code.getBottom10Bits() != 0) { + _CurrentBlock[0] = + _code.getBottom10Bits() * _aiQuantizationTable[0]; + iCurrentBlockNonZeroCount = 1; + iCurrentBlockLastNonZeroPosition = 0; + } else { + iCurrentBlockNonZeroCount = 0; + iCurrentBlockLastNonZeroPosition = -1; + } + assert !DEBUG || setPrequantValue(0, _code.getBottom10Bits()); + iCurrentBlockQscale = _code.getTop6Bits(); + iCurrentBlockVectorPosition = 0; + + while (!mdecInStream.readMdecCode(_code)) { assert !DEBUG || debugPrintln(_code.toString()); - writeEndOfBlock(iMacBlk, iBlock, - iCurrentBlockNonZeroCount, - iCurrentBlockLastNonZeroPosition); + //////////////////////////////////////////////////////// + iCurrentBlockVectorPosition += _code.getTop6Bits() + 1; + + int iRevZigZagMatrixPos; + try { + // Reverse Zig-Zag + iRevZigZagMatrixPos = MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[iCurrentBlockVectorPosition]; + } catch (ArrayIndexOutOfBoundsException ex) { + MdecContext.MacroBlockPixel macBlkXY = context.getMacroBlockPixel(); + throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_BLOCK_NAME( + iCurrentBlockVectorPosition, + context.getTotalMacroBlocksRead(), macBlkXY.x, macBlkXY.y, context.getCurrentBlock().ordinal(), context.getCurrentBlock().name()), + ex); + } + + if (_code.getBottom10Bits() != 0) { + + assert !DEBUG || setPrequantValue(iRevZigZagMatrixPos, _code.getBottom10Bits()); + // Dequantize + _CurrentBlock[iRevZigZagMatrixPos] = + (_code.getBottom10Bits() + * _aiQuantizationTable[iRevZigZagMatrixPos] + * iCurrentBlockQscale) / 8.0; + iCurrentBlockNonZeroCount++; + iCurrentBlockLastNonZeroPosition = iRevZigZagMatrixPos; + + } + //////////////////////////////////////////////////////// + context.nextCode(); } - iMacBlk++; + assert !DEBUG || debugPrintln(_code.toString()); + + writeEndOfBlock(context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + iCurrentBlockNonZeroCount, + iCurrentBlockLastNonZeroPosition); + + context.nextCodeEndBlock(); } } } finally { // in case an exception occured // fill in any remaining data with zeros - int iTotalMacBlks = _iMacBlockWidth * _iMacBlockHeight; // pickup where decoding left off - for (; iMacBlk < iTotalMacBlks; iMacBlk++) { - for (; iBlock < 6; iBlock++) { - writeEndOfBlock(iMacBlk, iBlock, 0, 0); - } - iBlock = 0; + while (context.getTotalMacroBlocksRead() < _iTotalMacBlocks) { + writeEndOfBlock(context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), 0, 0); + context.nextCodeEndBlock(); } + + mdecInStream.logIfAny0AcCoefficient(); } } - private boolean debugPrintBlock(String sMsg) { + private boolean debugPrintBlock(@Nonnull String sMsg) { System.out.println(sMsg); for (int i = 0; i < 8; i++) { System.out.print("[ "); @@ -192,17 +219,17 @@ private void writeEndOfBlock(int iMacroBlock, int iBlock, int iOutOffset, iOutWidth; switch (iBlock) { case 0: - outputBuffer = _CrBuffer; + outputBuffer = _adblDecodedCrBuffer; iOutOffset = _aiChromaMacBlkOfsLookup[iMacroBlock]; iOutWidth = CW; break; case 1: - outputBuffer = _CbBuffer; + outputBuffer = _dblDecodedCbBuffer; iOutOffset = _aiChromaMacBlkOfsLookup[iMacroBlock]; iOutWidth = CW; break; default: - outputBuffer = _LumaBuffer; + outputBuffer = _adblDecodedLumaBuffer; iOutOffset = _aiLumaBlkOfsLookup[iMacroBlock*4 + iBlock-2]; iOutWidth = W; } @@ -225,63 +252,53 @@ private void writeEndOfBlock(int iMacroBlock, int iBlock, } final static boolean YUV_TESTS = false; - - public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, + + public void readDecodedRgb(int iDestWidth, int iDestHeight, @Nonnull int[] aiDest, int iOutStart, int iOutStride) { - if ((iDestWidth % 2) != 0) - throw new IllegalArgumentException("Image width must be multiple of 2."); - if ((iDestHeight % 2) != 0) - throw new IllegalArgumentException("Image height must be multiple of 2."); + switch (_upsampler) { + case NearestNeighbor: + nearestNeighborUpsample(_adblDecodedCrBuffer, _adblTempUpsampledCr); + nearestNeighborUpsample(_dblDecodedCbBuffer, _adblTempUpsampledCb); + break; + case Bilinear: + bilinearUpsample(_adblDecodedCrBuffer, _adblTempUpsampledCr); + bilinearUpsample(_dblDecodedCbBuffer, _adblTempUpsampledCb); + break; + default: + _resampler.setFilter(_upsampler._filter); + _resampler.doFilter(_adblDecodedCrBuffer, CW, CH, _adblTempUpsampledCr); + _resampler.doFilter(_dblDecodedCbBuffer, CW, CH, _adblTempUpsampledCb); + } - final Rec601YCbCr rec601ycc = new Rec601YCbCr(); // for YUV_TESTS - final PsxYCbCr psxycc = new PsxYCbCr(); - final RGB rgb1 = new RGB(), rgb2 = new RGB(), rgb3 = new RGB(), rgb4 = new RGB(); - - final int W_x2 = W*2, iOutStride_x2 = iOutStride*2; - - int iLumaLineOfsStart = 0, iChromaLineOfsStart = 0, - iDestLineOfsStart = iOutStart; - for (int iY=0; iY < iDestHeight; - iY+=2, - iLumaLineOfsStart+=W_x2, iChromaLineOfsStart+=CW, - iDestLineOfsStart+=iOutStride_x2) + RGB rgb = new RGB(); + double y, cb, cr; + + for (int iY = 0, iSrcLineOfsStart=0, iDestLineOfsStart=iOutStart; + iY < iDestHeight; + iY++, iSrcLineOfsStart+=W, iDestLineOfsStart+=iOutStride) { - // writes 2 lines at a time - int iSrcLumaOfs1 = iLumaLineOfsStart, - iSrcLumaOfs2 = iLumaLineOfsStart + W, - iSrcChromaOfs = iChromaLineOfsStart, - iDestOfs1 = iDestLineOfsStart, - iDestOfs2 = iDestLineOfsStart + iOutStride; - for (int iX=0; + for (int iX=0, iSrcOfs=iSrcLineOfsStart, iDestOfs=iDestLineOfsStart; iX < iDestWidth; - iX+=2, iSrcChromaOfs++) + iX++, iSrcOfs++, iDestOfs++) { - psxycc.cr = _CrBuffer[iSrcChromaOfs]; - psxycc.cb = _CbBuffer[iSrcChromaOfs]; - - psxycc.y1 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y2 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y3 = _LumaBuffer[iSrcLumaOfs2++]; - psxycc.y4 = _LumaBuffer[iSrcLumaOfs2++]; + y = _adblDecodedLumaBuffer[iSrcOfs]; + cb = _adblTempUpsampledCb[iSrcOfs]; + cr = _adblTempUpsampledCr[iSrcOfs]; if (YUV_TESTS) { System.err.println("###>>!! YUV_TEST CONVERTING TO Rec601 THEN TO RGB !!<<###"); - psxycc.toRec_601_YCbCr(rec601ycc); - rec601ycc.toRgb(rgb1, rgb2, rgb3, rgb4); + Rec601YCbCr.toRgb(y, cb, cr, rgb); } else { - psxycc.toRgb(rgb1, rgb2, rgb3, rgb4); + PsxYCbCr.toRgb(y, cb, cr, rgb); } - aiDest[iDestOfs1++] = rgb1.toInt(); - aiDest[iDestOfs1++] = rgb2.toInt(); - aiDest[iDestOfs2++] = rgb3.toInt(); - aiDest[iDestOfs2++] = rgb4.toInt(); + aiDest[iDestOfs] = rgb.toInt(); } } } - public void readDecoded_Rec601_YCbCr420(YCbCrImage ycc) { + public void readDecoded_Rec601_YCbCr420(@Nonnull YCbCrImage ycc) { final int WIDTH = ycc.getWidth(), HEIGHT = ycc.getHeight(); @@ -301,13 +318,13 @@ public void readDecoded_Rec601_YCbCr420(YCbCrImage ycc) { int iSrcChromaOfs = iChromaLineOfsStart; for (int iX=0, iCX=0; iX < WIDTH; iX+=2, iCX++, iSrcChromaOfs++) { - psxycc.cr = _CrBuffer[iSrcChromaOfs]; - psxycc.cb = _CbBuffer[iSrcChromaOfs]; + psxycc.cr = _adblDecodedCrBuffer[iSrcChromaOfs]; + psxycc.cb = _dblDecodedCbBuffer[iSrcChromaOfs]; - psxycc.y1 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y3 = _LumaBuffer[iSrcLumaOfs2++]; - psxycc.y2 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y4 = _LumaBuffer[iSrcLumaOfs2++]; + psxycc.y1 = _adblDecodedLumaBuffer[iSrcLumaOfs1++]; + psxycc.y3 = _adblDecodedLumaBuffer[iSrcLumaOfs2++]; + psxycc.y2 = _adblDecodedLumaBuffer[iSrcLumaOfs1++]; + psxycc.y4 = _adblDecodedLumaBuffer[iSrcLumaOfs2++]; psxycc.toRec_601_YCbCr(recycc); @@ -326,7 +343,7 @@ public void readDecoded_Rec601_YCbCr420(YCbCrImage ycc) { /** * @see PsxYCbCr#toRec_JFIF_YCbCr(jpsxdec.formats.Rec601YCbCr) */ - public void readDecoded_JFIF_YCbCr420(YCbCrImage ycc) { + public void readDecoded_JFIF_YCbCr420(@Nonnull YCbCrImage ycc) { final int WIDTH = ycc.getWidth(), HEIGHT = ycc.getHeight(); @@ -346,13 +363,13 @@ public void readDecoded_JFIF_YCbCr420(YCbCrImage ycc) { int iSrcChromaOfs = iChromaLineOfsStart; for (int iX=0, iCX=0; iX < WIDTH; iX+=2, iCX++, iSrcChromaOfs++) { - psxycc.cr = _CrBuffer[iSrcChromaOfs]; - psxycc.cb = _CbBuffer[iSrcChromaOfs]; + psxycc.cr = _adblDecodedCrBuffer[iSrcChromaOfs]; + psxycc.cb = _dblDecodedCbBuffer[iSrcChromaOfs]; - psxycc.y1 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y3 = _LumaBuffer[iSrcLumaOfs2++]; - psxycc.y2 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y4 = _LumaBuffer[iSrcLumaOfs2++]; + psxycc.y1 = _adblDecodedLumaBuffer[iSrcLumaOfs1++]; + psxycc.y3 = _adblDecodedLumaBuffer[iSrcLumaOfs2++]; + psxycc.y2 = _adblDecodedLumaBuffer[iSrcLumaOfs1++]; + psxycc.y4 = _adblDecodedLumaBuffer[iSrcLumaOfs2++]; psxycc.toRec_JFIF_YCbCr(recycc); @@ -378,4 +395,91 @@ else if (lng > 255) } + + private void nearestNeighborUpsample(@Nonnull double[] in, @Nonnull double[] out) { + int outOfs = 0; + int inOfs = 0; + for (int inY=0; inY < CH; inY++) { + // copy a line, scaling horizontally + for (int inX=0; inX < CW; inX++) { + out[outOfs++] = in[inOfs]; + out[outOfs++] = in[inOfs]; + inOfs++; + } + // duplicate that horizontally scaled line, thus scaling it vertically + System.arraycopy(out, outOfs-W, out, outOfs, W); + outOfs += W; + } + } + + private void bilinearUpsample(@Nonnull double[] in, @Nonnull double[] out) { + // corners + out[0 + 0 *W] = in[0 + 0 *CW]; + out[W-1 + 0 *W] = in[CW-1 + 0 *CW]; + out[0 + (H-1)*W] = in[0 + (CH-1)*CW]; + out[W-1 + (H-1)*W] = in[CW-1 + (CH-1)*CW]; + + // vertical edges + for (int i = 0; i < 2; i++) { + int inX, outX; + if (i == 0) { + outX = 0; + inX = 0; + } else { + outX = W - 1; + inX = CW - 1; + } + for (int inY = 0; inY < CH-1; inY++) { + double c1 = in[inX + inY *CW], + c2 = in[inX + (inY+1)*CW]; + int outY = 1 + inY*2; + out[outX + outY *W] = c1 * 0.75 + c2 * 0.25; + out[outX + (outY+1)*W] = c1 * 0.25 + c2 * 0.75; + } + } + + // horizontal edges + for (int i = 0; i < 2; i++) { + int inY, outY; + if (i == 0) { + outY = 0; + inY = 0; + } else { + outY = H - 1; + inY = CH - 1; + } + for (int inX = 0; inX < CW-1; inX++) { + double c1 = in[inX + inY*CW], + c2 = in[inX+1 + inY*CW]; + int outX = 1+ inX*2; + out[outX + outY*W] = c1 * 0.75 + c2 * 0.25; + out[outX+1 + outY*W] = c1 * 0.25 + c2 * 0.75; + } + } + + // the meat in the middle + for (int inY=0; inY < CH-1; inY++) { + int inOfs = inY*CW; + int outOfs = ((inY*2)+1)*W + 1; + double c1, c2 = in[inOfs], + c3, c4 = in[inOfs+CW]; + inOfs++; + for (int inX=0; inX < CW-1; inX++, inOfs++) { + c1 = c2; c2 = in[inOfs]; + c3 = c4; c4 = in[inOfs+CW]; + double c1_c4_mul_3_16 = (c1 + c4) * (3. / 16.), + c2_c3_mul_3_16 = (c2 + c3) * (3. / 16.); + out[outOfs ]= c1 * (9. / 16.) + c2_c3_mul_3_16 + c4 * (1. / 16.); + out[outOfs+W]= c1_c4_mul_3_16 + c2 * (1. / 16.) + c3 * (9. / 16.); + outOfs++; + out[outOfs ]= c1_c4_mul_3_16 + c2 * (9. / 16.) + c3 * (1. / 16.); + out[outOfs+W]= c1 * (1. / 16.) + c2_c3_mul_3_16 + c4 * (9. / 16.); + outOfs++; + } + } + + } + + + } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double_interpolate.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double_interpolate.java deleted file mode 100644 index ec633de..0000000 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_double_interpolate.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 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.psxvideo.mdec; - -import com.mortennobel.imagescaling.ResampleFilter; -import com.mortennobel.imagescaling.ResampleFilters; -import com.mortennobel.imagescaling.ResampleOp; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; -import jpsxdec.formats.RGB; -import jpsxdec.i18n.I; -import jpsxdec.i18n.ILocalizedMessage; -import jpsxdec.psxvideo.PsxYCbCr; -import jpsxdec.psxvideo.mdec.idct.IDCT_double; - -/** A full Java, double-precision, floating point implementation of the - * PlayStation 1 MDEC chip with interpolation used in chroma upsampling. - *

        - * Default upsampling method is Bicubic. - *

        - * To understand how that is helpful, read up on how 4:2:0 YCbCr format - * works, and how it is then converted to RGB. - */ -public class MdecDecoder_double_interpolate extends MdecDecoder_double { - - public enum Upsampler { - /** i.e. Box */ - NearestNeighbor(I.CHROMA_UPSAMPLE_NEAR_NEIGHBOR_DESCRIPTION(), - I.CHROMA_UPSAMPLE_NEAR_NEIGHBOR_CMDLINE(), null), - - /** i.e. Triangle */ - Bilinear(I.CHROMA_UPSAMPLE_BILINEAR_DESCRIPTION(), - I.CHROMA_UPSAMPLE_BILINEAR_CMDLINE(), null), - - Bicubic(I.CHROMA_UPSAMPLE_BICUBIC_DESCRIPTION(), - I.CHROMA_UPSAMPLE_BICUBIC_CMDLINE(), ResampleFilters.getBiCubicFilter()), - - Bell(I.CHROMA_UPSAMPLE_BELL_DESCRIPTION(), - I.CHROMA_UPSAMPLE_BELL_CMDLINE(), ResampleFilters.getBellFilter()), - - Mitchell(I.CHROMA_UPSAMPLE_MITCHELL_DESCRIPTION(), - I.CHROMA_UPSAMPLE_MITCHELL_CMDLINE(), ResampleFilters.getMitchellFilter()), - - BSpline(I.CHROMA_UPSAMPLE_BSPLINE_DESCRIPTION(), - I.CHROMA_UPSAMPLE_BSPLINE_CMDLINE(), ResampleFilters.getBSplineFilter()), - - Lanczos3(I.CHROMA_UPSAMPLE_LANCZOS3_DESCRIPTION(), - I.CHROMA_UPSAMPLE_LANCZOS3_CMDLINE(), ResampleFilters.getLanczos3Filter()), - - Hermite(I.CHROMA_UPSAMPLE_HERMITE_DESCRIPTION(), - I.CHROMA_UPSAMPLE_HERMITE_CMDLINE(), ResampleFilters.getHermiteFilter()); - - @Nonnull - private final ILocalizedMessage _description; - private final ILocalizedMessage _cmdLine; - @CheckForNull - private final ResampleFilter _filter; - - private Upsampler(@Nonnull ILocalizedMessage description, - @Nonnull ILocalizedMessage cmdLine, - @CheckForNull ResampleFilter filter) - { - _description = description; - _cmdLine = cmdLine; - _filter = filter; - } - - public static Upsampler fromCmdLine(String sCmdLine) { - Upsampler up = null; - for (Upsampler upsampler : values()) { - if (upsampler.getCmdLine().equalsIgnoreCase(sCmdLine)) { - up = upsampler; - break; - } - } - return up; - } - - public ILocalizedMessage getDescription() { - return _description; - } - - public ILocalizedMessage getCmdLine() { - return _cmdLine; - } - - public ILocalizedMessage getCmdLineHelp() { - if (_cmdLine.equals(_description)) - return _cmdLine; - else - return I.CHROMA_UPSAMPLE_CMDLINE_HELP(_cmdLine, _description); - } - - /** {@inheritDoc} - *

        - * Used in GUI list so must be localized. */ - @Override - public String toString() { - return getDescription().getLocalizedMessage(); - } - } - - // ==================================================================== - - /** Temp buffer for upsampled Cr. */ - private final double[] _adblUpCr; - /** Temp buffer for upsampled Cb. */ - private final double[] _adblUpCb; - - private final ResampleOp _resampler; - private Upsampler _upsampler = Upsampler.Bicubic; - - public MdecDecoder_double_interpolate(IDCT_double idct, int iWidth, int iHeight) { - super(idct, iWidth, iHeight); - - _adblUpCb = new double[W * H]; - _adblUpCr = new double[W * H]; - - _resampler = new ResampleOp(); - _resampler.setNumberOfThreads(1); - } - - public void setResampler(Upsampler u) { - _upsampler = u; - } - - @Override - public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, - int iOutStart, int iOutStride) - { - switch (_upsampler) { - case NearestNeighbor: - nearestNeighborUpsample(_CrBuffer, _adblUpCr); - nearestNeighborUpsample(_CbBuffer, _adblUpCb); - break; - case Bilinear: - bilinearUpsample(_CrBuffer, _adblUpCr); - bilinearUpsample(_CbBuffer, _adblUpCb); - break; - default: - _resampler.setFilter(_upsampler._filter); - _resampler.doFilter(_CrBuffer, CW, CH, _adblUpCr); - _resampler.doFilter(_CbBuffer, CW, CH, _adblUpCb); - } - - RGB rgb = new RGB(); - double y, cb, cr; - - for (int iY = 0, iSrcLineOfsStart=0, iDestLineOfsStart=iOutStart; - iY < iDestHeight; - iY++, iSrcLineOfsStart+=W, iDestLineOfsStart+=iOutStride) - { - for (int iX=0, iSrcOfs=iSrcLineOfsStart, iDestOfs=iDestLineOfsStart; - iX < iDestWidth; - iX++, iSrcOfs++, iDestOfs++) - { - y = _LumaBuffer[iSrcOfs]; - cb = _adblUpCb[iSrcOfs]; - cr = _adblUpCr[iSrcOfs]; - PsxYCbCr.toRgb(y, cb, cr, rgb); - aiDest[iDestOfs] = rgb.toInt(); - } - } - } - - private void nearestNeighborUpsample(double[] in, double[] out) { - int outOfs = 0; - int inOfs = 0; - for (int inY=0; inY < CH; inY++) { - // copy a line, scaling horizontally - for (int inX=0; inX < CW; inX++) { - out[outOfs++] = in[inOfs]; - out[outOfs++] = in[inOfs]; - inOfs++; - } - // duplicate that horizontally scaled line, thus scaling it vertically - System.arraycopy(out, outOfs-W, out, outOfs, W); - outOfs += W; - } - } - - private void bilinearUpsample(double[] in, double[] out) { - // corners - out[0 + 0 *W] = in[0 + 0 *CW]; - out[W-1 + 0 *W] = in[CW-1 + 0 *CW]; - out[0 + (H-1)*W] = in[0 + (CH-1)*CW]; - out[W-1 + (H-1)*W] = in[CW-1 + (CH-1)*CW]; - - // vertical edges - for (int i = 0; i < 2; i++) { - int inX, outX; - if (i == 0) { - outX = 0; - inX = 0; - } else { - outX = W - 1; - inX = CW - 1; - } - for (int inY = 0; inY < CH-1; inY++) { - double c1 = in[inX + inY *CW], - c2 = in[inX + (inY+1)*CW]; - int outY = 1 + inY*2; - out[outX + outY *W] = c1 * 0.75 + c2 * 0.25; - out[outX + (outY+1)*W] = c1 * 0.25 + c2 * 0.75; - } - } - - // horizontal edges - for (int i = 0; i < 2; i++) { - int inY, outY; - if (i == 0) { - outY = 0; - inY = 0; - } else { - outY = H - 1; - inY = CH - 1; - } - for (int inX = 0; inX < CW-1; inX++) { - double c1 = in[inX + inY*CW], - c2 = in[inX+1 + inY*CW]; - int outX = 1+ inX*2; - out[outX + outY*W] = c1 * 0.75 + c2 * 0.25; - out[outX+1 + outY*W] = c1 * 0.25 + c2 * 0.75; - } - } - - // the meat in the middle - for (int inY=0; inY < CH-1; inY++) { - int inOfs = inY*CW; - int outOfs = ((inY*2)+1)*W + 1; - double c1, c2 = in[inOfs], - c3, c4 = in[inOfs+CW]; - inOfs++; - for (int inX=0; inX < CW-1; inX++, inOfs++) { - c1 = c2; c2 = in[inOfs]; - c3 = c4; c4 = in[inOfs+CW]; - double c1_c4_mul_3_16 = (c1 + c4) * (3. / 16.), - c2_c3_mul_3_16 = (c2 + c3) * (3. / 16.); - out[outOfs ]= c1 * (9. / 16.) + c2_c3_mul_3_16 + c4 * (1. / 16.); - out[outOfs+W]= c1_c4_mul_3_16 + c2 * (1. / 16.) + c3 * (9. / 16.); - outOfs++; - out[outOfs ]= c1_c4_mul_3_16 + c2 * (9. / 16.) + c3 * (1. / 16.); - out[outOfs+W]= c1 * (1. / 16.) + c2_c3_mul_3_16 + c4 * (9. / 16.); - outOfs++; - } - } - - } - - -} diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_int.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_int.java index 03227dc..5fa6eae 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_int.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecDecoder_int.java @@ -38,6 +38,7 @@ package jpsxdec.psxvideo.mdec; import java.util.Arrays; +import javax.annotation.Nonnull; import jpsxdec.formats.RGB; import jpsxdec.psxvideo.PsxYCbCr_int; import jpsxdec.psxvideo.mdec.idct.IDCT_int; @@ -47,129 +48,126 @@ * examination, you can't really tell. It's also significantly faster. */ public class MdecDecoder_int extends MdecDecoder { - protected final IDCT_int _idct; + private final IDCT_int _idct; - protected final int[] _CrBuffer; - protected final int[] _CbBuffer; - protected final int[] _LumaBuffer; + private final int[] _aiCrBuffer; + private final int[] _aiCbBuffer; + private final int[] _aiLumaBuffer; /** Matrix of 8x8 coefficient values. */ protected final int[] _CurrentBlock = new int[64]; - public MdecDecoder_int(IDCT_int idct, int iWidth, int iHeight) { + public MdecDecoder_int(@Nonnull IDCT_int idct, int iWidth, int iHeight) { super(iWidth, iHeight); _idct = idct; - _CrBuffer = new int[CW*CH]; - _CbBuffer = new int[_CrBuffer.length]; - _LumaBuffer = new int[W*H]; + _aiCrBuffer = new int[CW*CH]; + _aiCbBuffer = new int[_aiCrBuffer.length]; + _aiLumaBuffer = new int[W*H]; } - public void decode(MdecInputStream mdecInStream) + public void decode(@Nonnull MdecInputStream sourceMdecInStream) throws MdecException.EndOfStream, MdecException.ReadCorruption { + Ac0Checker mdecInStream = Ac0Checker.wrapWithChecker(sourceMdecInStream, false); int iCurrentBlockQscale; int iCurrentBlockVectorPosition; int iCurrentBlockNonZeroCount; int iCurrentBlockLastNonZeroPosition; - int iMacBlk = 0, iBlock = 0; + MdecContext context = new MdecContext(_iMacBlockHeight); try { // decode all the macro blocks of the image - for (int iMacBlkX = 0; iMacBlkX < _iMacBlockWidth; iMacBlkX ++) - { - for (int iMacBlkY = 0; iMacBlkY < _iMacBlockHeight; iMacBlkY ++) - { - // debug - assert !DEBUG || debugPrintln(String.format("############### Decoding macro block %d (%d, %d) ###############", - iMacBlk, iMacBlkX, iMacBlkY)); + while (context.getTotalMacroBlocksRead() < _iTotalMacBlocks) { + // debug + assert !DEBUG || debugPrintln(String.format("############### Decoding macro block %d %s ###############", + context.getTotalMacroBlocksRead(), context.getMacroBlockPixel())); - for (iBlock = 0; iBlock < 6; iBlock++) { + for (int iBlock = 0; iBlock < MdecBlock.count(); iBlock++) { - assert !DEBUG || debugPrintln(String.format("=========== Decoding block %s ===========", - BLOCK_NAMES[iBlock])); - - Arrays.fill(_CurrentBlock, 0); - mdecInStream.readMdecCode(_code); + assert !DEBUG || debugPrintln("=========== Decoding block "+context.getCurrentBlock()+" ==========="); - assert !DEBUG || debugPrintln("Qscale & DC " + _code); + Arrays.fill(_CurrentBlock, 0); + mdecInStream.readMdecCode(_code); - if (_code.getBottom10Bits() != 0) { - _CurrentBlock[0] = - _code.getBottom10Bits() * _aiQuantizationTable[0]; - iCurrentBlockNonZeroCount = 1; - iCurrentBlockLastNonZeroPosition = 0; - } else { - iCurrentBlockNonZeroCount = 0; - iCurrentBlockLastNonZeroPosition = -1; - } - assert !DEBUG || setPrequantValue(0, _code.getBottom10Bits()); - iCurrentBlockQscale = _code.getTop6Bits(); - iCurrentBlockVectorPosition = 0; - - while (!mdecInStream.readMdecCode(_code)) { - - assert !DEBUG || debugPrintln(_code.toString()); - - //////////////////////////////////////////////////////// - iCurrentBlockVectorPosition += _code.getTop6Bits() + 1; - - int iRevZigZagMatrixPos; - try { - // Reverse Zig-Zag - iRevZigZagMatrixPos = MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[iCurrentBlockVectorPosition]; - } catch (ArrayIndexOutOfBoundsException ex) { - throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_BLOCK_NAME( - iCurrentBlockVectorPosition, - iMacBlk, iMacBlkX, iMacBlkY, iBlock, BLOCK_NAMES[iBlock]), - ex); - } - - if (_code.getBottom10Bits() != 0) { - - assert !DEBUG || setPrequantValue(iRevZigZagMatrixPos, _code.getBottom10Bits()); - // Dequantize - _CurrentBlock[iRevZigZagMatrixPos] = - (_code.getBottom10Bits() - * _aiQuantizationTable[iRevZigZagMatrixPos] - * iCurrentBlockQscale + 4) >> 3; - // i >> 3 == (int)Math.floor(i / 8.0) - // (i + 4) >> 3 == (int)Math.round(i / 8.0) - iCurrentBlockNonZeroCount++; - iCurrentBlockLastNonZeroPosition = iRevZigZagMatrixPos; - - } - //////////////////////////////////////////////////////// - } + assert !DEBUG || debugPrintln("Qscale & DC " + _code); + + if (_code.getBottom10Bits() != 0) { + _CurrentBlock[0] = + _code.getBottom10Bits() * _aiQuantizationTable[0]; + iCurrentBlockNonZeroCount = 1; + iCurrentBlockLastNonZeroPosition = 0; + } else { + iCurrentBlockNonZeroCount = 0; + iCurrentBlockLastNonZeroPosition = -1; + } + assert !DEBUG || setPrequantValue(0, _code.getBottom10Bits()); + iCurrentBlockQscale = _code.getTop6Bits(); + iCurrentBlockVectorPosition = 0; + + while (!mdecInStream.readMdecCode(_code)) { assert !DEBUG || debugPrintln(_code.toString()); - writeEndOfBlock(iMacBlk, iBlock, - iCurrentBlockNonZeroCount, - iCurrentBlockLastNonZeroPosition); + //////////////////////////////////////////////////////// + iCurrentBlockVectorPosition += _code.getTop6Bits() + 1; + + int iRevZigZagMatrixPos; + try { + // Reverse Zig-Zag + iRevZigZagMatrixPos = MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[iCurrentBlockVectorPosition]; + } catch (ArrayIndexOutOfBoundsException ex) { + MdecContext.MacroBlockPixel macBlkXY = context.getMacroBlockPixel(); + throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_BLOCK_NAME( + iCurrentBlockVectorPosition, + context.getTotalMacroBlocksRead(), macBlkXY.x, macBlkXY.y, context.getCurrentBlock().ordinal(), context.getCurrentBlock().name()), + ex); + } + + if (_code.getBottom10Bits() != 0) { + + assert !DEBUG || setPrequantValue(iRevZigZagMatrixPos, _code.getBottom10Bits()); + // Dequantize + _CurrentBlock[iRevZigZagMatrixPos] = + (_code.getBottom10Bits() + * _aiQuantizationTable[iRevZigZagMatrixPos] + * iCurrentBlockQscale + 4) >> 3; + // i >> 3 == (int)Math.floor(i / 8.0) + // (i + 4) >> 3 == (int)Math.round(i / 8.0) + iCurrentBlockNonZeroCount++; + iCurrentBlockLastNonZeroPosition = iRevZigZagMatrixPos; + + } + //////////////////////////////////////////////////////// + context.nextCode(); } - iMacBlk++; + assert !DEBUG || debugPrintln(_code.toString()); + + writeEndOfBlock(context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), + iCurrentBlockNonZeroCount, + iCurrentBlockLastNonZeroPosition); + + context.nextCodeEndBlock(); } } } finally { // in case an exception occured // fill in any remaining data with zeros - int iTotalMacBlks = _iMacBlockWidth * _iMacBlockHeight; // pickup where decoding left off - for (; iMacBlk < iTotalMacBlks; iMacBlk++) { - for (; iBlock < 6; iBlock++) { - writeEndOfBlock(iMacBlk, iBlock, 0, 0); - } - iBlock = 0; + while (context.getTotalMacroBlocksRead() < _iTotalMacBlocks) { + writeEndOfBlock(context.getTotalMacroBlocksRead(), context.getCurrentBlock().ordinal(), 0, 0); + context.nextCodeEndBlock(); } + + mdecInStream.logIfAny0AcCoefficient(); } } - private boolean debugPrintBlock(String sMsg) { + private boolean debugPrintBlock(@Nonnull String sMsg) { System.out.println(sMsg); for (int i = 0; i < 8; i++) { System.out.print("[ "); @@ -192,17 +190,17 @@ private void writeEndOfBlock(int iMacroBlock, int iBlock, int iOutOffset, iOutWidth; switch (iBlock) { case 0: - outputBuffer = _CrBuffer; + outputBuffer = _aiCrBuffer; iOutOffset = _aiChromaMacBlkOfsLookup[iMacroBlock]; iOutWidth = CW; break; case 1: - outputBuffer = _CbBuffer; + outputBuffer = _aiCbBuffer; iOutOffset = _aiChromaMacBlkOfsLookup[iMacroBlock]; iOutWidth = CW; break; default: - outputBuffer = _LumaBuffer; + outputBuffer = _aiLumaBuffer; iOutOffset = _aiLumaBlkOfsLookup[iMacroBlock*4 + iBlock-2]; iOutWidth = W; } @@ -224,23 +222,21 @@ private void writeEndOfBlock(int iMacroBlock, int iBlock, } - public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, + public void readDecodedRgb(int iDestWidth, int iDestHeight, @Nonnull int[] aiDest, int iOutStart, int iOutStride) { - if ((iDestWidth % 2) != 0) - throw new IllegalArgumentException("Image width must be multiple of 2."); - if ((iDestHeight % 2) != 0) - throw new IllegalArgumentException("Image height must be multiple of 2."); - final PsxYCbCr_int psxycc = new PsxYCbCr_int(); final RGB rgb1 = new RGB(), rgb2 = new RGB(), rgb3 = new RGB(), rgb4 = new RGB(); final int W_x2 = W*2, iOutStride_x2 = iOutStride*2; + final int iDestWidthSub1 = iDestWidth - 1; + final int iDestHeightSub1 = iDestHeight - 1; + int iLumaLineOfsStart = 0, iChromaLineOfsStart = 0, iDestLineOfsStart = iOutStart; - for (int iY=0; iY < iDestHeight; - iY+=2, + int iY=0; + for (; iY < iDestHeightSub1; iY+=2, iLumaLineOfsStart+=W_x2, iChromaLineOfsStart+=CW, iDestLineOfsStart+=iOutStride_x2) { @@ -250,17 +246,18 @@ public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, iSrcChromaOfs = iChromaLineOfsStart, iDestOfs1 = iDestLineOfsStart, iDestOfs2 = iDestLineOfsStart + iOutStride; - for (int iX=0; - iX < iDestWidth; - iX+=2, iSrcChromaOfs++) + + int iX=0; + for (; iX < iDestWidthSub1; iX+=2, + iSrcChromaOfs++) { - psxycc.cr = _CrBuffer[iSrcChromaOfs]; - psxycc.cb = _CbBuffer[iSrcChromaOfs]; + psxycc.cr = _aiCrBuffer[iSrcChromaOfs]; + psxycc.cb = _aiCbBuffer[iSrcChromaOfs]; - psxycc.y1 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y2 = _LumaBuffer[iSrcLumaOfs1++]; - psxycc.y3 = _LumaBuffer[iSrcLumaOfs2++]; - psxycc.y4 = _LumaBuffer[iSrcLumaOfs2++]; + psxycc.y1 = _aiLumaBuffer[iSrcLumaOfs1++]; + psxycc.y2 = _aiLumaBuffer[iSrcLumaOfs1++]; + psxycc.y3 = _aiLumaBuffer[iSrcLumaOfs2++]; + psxycc.y4 = _aiLumaBuffer[iSrcLumaOfs2++]; psxycc.toRgb(rgb1, rgb2, rgb3, rgb4); @@ -269,6 +266,63 @@ public void readDecodedRgb(int iDestWidth, int iDestHeight, int[] aiDest, aiDest[iDestOfs2++] = rgb3.toInt(); aiDest[iDestOfs2++] = rgb4.toInt(); } + + if (iX < iDestWidth) { + // if the width is odd, add 2 pixels + psxycc.cr = _aiCrBuffer[iSrcChromaOfs]; + psxycc.cb = _aiCbBuffer[iSrcChromaOfs]; + + psxycc.y1 = _aiLumaBuffer[iSrcLumaOfs1]; + psxycc.y2 = _aiLumaBuffer[iSrcLumaOfs1]; + psxycc.y3 = _aiLumaBuffer[iSrcLumaOfs2]; + psxycc.y4 = _aiLumaBuffer[iSrcLumaOfs2]; + + psxycc.toRgb(rgb1, rgb2, rgb3, rgb4); // rgb2,4 ignored + + aiDest[iDestOfs1] = rgb1.toInt(); + aiDest[iDestOfs2] = rgb3.toInt(); + } + } + + if (iY < iDestHeight) { + // if the height is odd, write 1 line + int iSrcLumaOfs1 = iLumaLineOfsStart, + iSrcLumaOfs2 = iLumaLineOfsStart + W, + iSrcChromaOfs = iChromaLineOfsStart, + iDestOfs1 = iDestLineOfsStart; + + int iX=0; + for (; iX < iDestWidthSub1; iX+=2, + iSrcChromaOfs++) + { + psxycc.cr = _aiCrBuffer[iSrcChromaOfs]; + psxycc.cb = _aiCbBuffer[iSrcChromaOfs]; + + psxycc.y1 = _aiLumaBuffer[iSrcLumaOfs1++]; + psxycc.y2 = _aiLumaBuffer[iSrcLumaOfs1++]; + psxycc.y3 = _aiLumaBuffer[iSrcLumaOfs2++]; + psxycc.y4 = _aiLumaBuffer[iSrcLumaOfs2++]; + + psxycc.toRgb(rgb1, rgb2, rgb3, rgb4); // rgb3,4 ignored + + aiDest[iDestOfs1++] = rgb1.toInt(); + aiDest[iDestOfs1++] = rgb2.toInt(); + } + + if (iX < iDestWidth) { + // if the width is odd, add 1 pixel + psxycc.cr = _aiCrBuffer[iSrcChromaOfs]; + psxycc.cb = _aiCbBuffer[iSrcChromaOfs]; + + psxycc.y1 = _aiLumaBuffer[iSrcLumaOfs1]; + psxycc.y2 = _aiLumaBuffer[iSrcLumaOfs1]; + psxycc.y3 = _aiLumaBuffer[iSrcLumaOfs2]; + psxycc.y4 = _aiLumaBuffer[iSrcLumaOfs2]; + + psxycc.toRgb(rgb1, rgb2, rgb3, rgb4); // rgb2,3,4 ignored + + aiDest[iDestOfs1] = rgb1.toInt(); + } } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecException.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecException.java index 1546bbf..8d916e7 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecException.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecException.java @@ -91,15 +91,15 @@ public static class EndOfStream extends Exception { public EndOfStream() { } - public EndOfStream(String message) { + public EndOfStream(@Nonnull String message) { super(message); } - public EndOfStream(Throwable cause) { + public EndOfStream(@Nonnull Throwable cause) { super(cause); } - public EndOfStream(String message, Throwable cause) { + public EndOfStream(@Nonnull String message, @Nonnull Throwable cause) { super(message, cause); } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStream.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStream.java index 7b84a99..553f4ef 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStream.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStream.java @@ -37,28 +37,24 @@ package jpsxdec.psxvideo.mdec; +import javax.annotation.Nonnull; + /** Read MDEC codes one at a time from a stream. * This is actually the coolest class in the entire jPSXdec codebase - * because it has allowed epic interoperability between mdec streams. */ -public abstract class MdecInputStream { + * because it has allowed epic interoperability between MDEC streams. */ +public interface MdecInputStream { /** Reads the next MDEC code from the stream into the provided * {@link MdecCode} object. * - * @return true if the EOD code is read. */ - public abstract boolean readMdecCode(MdecCode code) + * @return true if the EOD code was read. */ + boolean readMdecCode(@Nonnull MdecCode code) throws MdecException.EndOfStream, MdecException.ReadCorruption; - /** 16-bit MDEC code indicating the end of a block. - * The equivalent MDEC value is (63, -512). */ - public final static int MDEC_END_OF_DATA = 0xFE00; - /** Top 6 bits of {@link #MDEC_END_OF_DATA}. */ - public final static int MDEC_END_OF_DATA_TOP6 = (MDEC_END_OF_DATA >> 10) & 63; - /** Bottom 10 bits of {@link #MDEC_END_OF_DATA}. */ - public final static int MDEC_END_OF_DATA_BOTTOM10 = (short)(MDEC_END_OF_DATA | 0xFC00); - - /** Standard quantization matrix for MDEC frames. */ - private static final int[] PSX_DEFAULT_QUANTIZATION_MATRIX = { + /** Standard quantization matrix uploaded to the MDEC chip by PlayStation + * games. It's theoretically possible that a game could upload different + * values, but it's never been seen in the wild. */ + public static final int[] PSX_DEFAULT_QUANTIZATION_MATRIX = { 2, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37, 19, 22, 26, 27, 29, 34, 34, 38, @@ -68,10 +64,6 @@ public abstract boolean readMdecCode(MdecCode code) 26, 27, 29, 34, 38, 46, 56, 69, 27, 29, 35, 38, 46, 56, 69, 83 }; - /** Retrieve a copy of the PSX default quantization matrix. */ - public static int[] getDefaultPsxQuantMatrixCopy() { - return PSX_DEFAULT_QUANTIZATION_MATRIX.clone(); - } /** Matrix of vector indexes in the order that vector values selected. */ public static final int[] ZIG_ZAG_LOOKUP_MATRIX = { @@ -93,156 +85,4 @@ public static int[] getDefaultPsxQuantMatrixCopy() { 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; - /** Represents a 16-bit code readable by the PSX MDEC chip. - * If the MDEC code is the first of a block, the top 6 bits indicate - * the block's quantization scale, and the bottom 10 bits indicate - * the "direct current" (DC) coefficient. - * If the MDEC code is not the first of a block, and it is - * not a {@link #MDEC_END_OF_DATA} code (0xFE00), then the top 6 bits indicate - * the number of zeros preceeding an "alternating current" (AC) coefficient, - * with the bottom 10 bits indicating a (usually) non-zero AC coefficient. */ - public static class MdecCode { - - /** Most significant 6 bits of the 16-bit MDEC code. - * Holds either a block's quantization scale or the - * count of zero AC coefficients leading up to a non-zero - * AC coefficient. */ - private int _iTop6Bits; - - /** Least significant 10 bits of the 16-bit MDEC code. - * Holds either the DC coefficient of a block or - * a non-zero AC coefficient. */ - private int _iBottom10Bits; - - /** Initializes to (0, 0). */ - public MdecCode() { - _iTop6Bits = 0; - _iBottom10Bits = 0; - } - - public MdecCode(int iTop6Bits, int iBottom10Bits) { - if (!validTop(iTop6Bits)) - throw new IllegalArgumentException("Invalid top 6 bits " + iTop6Bits); - if (!validBottom(iBottom10Bits)) - throw new IllegalArgumentException("Invalid bottom 10 bits " + iBottom10Bits); - _iTop6Bits = iTop6Bits; - _iBottom10Bits = iBottom10Bits; - } - - /** Extract the top 6 bit and bottom 10 bit values from 16 bits */ - public MdecCode(int iMdecWord) { - set(iMdecWord); - } - - public void set(MdecCode other) { - _iTop6Bits = other._iTop6Bits; - _iBottom10Bits = other._iBottom10Bits; - } - - public int getBottom10Bits() { - return _iBottom10Bits; - } - - public void setBottom10Bits(int iBottom10Bits) { - assert validBottom(iBottom10Bits); - _iBottom10Bits = iBottom10Bits; - } - - public int getTop6Bits() { - return _iTop6Bits; - } - - public void setTop6Bits(int iTop6Bits) { - assert validTop(iTop6Bits); - _iTop6Bits = iTop6Bits; - } - - public void setBits(int iTop6, int iBottom10) { - assert validTop(_iTop6Bits) && validBottom(_iBottom10Bits); - _iTop6Bits = iTop6; - _iBottom10Bits = iBottom10; - } - - public void set(int iMdecWord) { - _iTop6Bits = ((iMdecWord >> 10) & 63); - _iBottom10Bits = (iMdecWord & 0x3FF); - if ((_iBottom10Bits & 0x200) == 0x200) { // is it negitive? - _iBottom10Bits -= 0x400; - } - } - - /** Combines the top 6 bits and bottom 10 bits into an unsigned 16 bit value. */ - public int toMdecWord() { - if (!validTop(_iTop6Bits)) - throw new IllegalStateException("MDEC code has invalid top 6 bits " + _iTop6Bits); - if (!validBottom(_iBottom10Bits)) - throw new IllegalStateException("MDEC code has invalid bottom 10 bits " + _iBottom10Bits); - return ((_iTop6Bits & 63) << 10) | (_iBottom10Bits & 0x3FF); - } - - /** Set the MDEC code to the special "End of Data" (EOD) value, - * indicating the end of a block. - * @see MdecInputStream#MDEC_END_OF_DATA */ - public void setToEndOfData() { - _iTop6Bits = MDEC_END_OF_DATA_TOP6; - _iBottom10Bits = MDEC_END_OF_DATA_BOTTOM10; - } - - /** Returns if this MDEC code is set to the special "End of Data" (EOD) - * value. - * @see MdecInputStream#MDEC_END_OF_DATA */ - public boolean isEOD() { - return (_iTop6Bits == MDEC_END_OF_DATA_TOP6 && - _iBottom10Bits == MDEC_END_OF_DATA_BOTTOM10); - } - - /** Returns if this MDEC code has valid values. - * As an optimization, many parameter checks are disabled, so - * this MDEC code could hold values that are be invalid. */ - public boolean isValid() { - return validTop(_iTop6Bits) && validBottom(_iBottom10Bits); - } - - /** Checks if the top 6 bits of an MDEC code are valid. */ - private static boolean validTop(int iTop6Bits) { - return iTop6Bits >= 0 && iTop6Bits <= 63; - } - /** Checks if the bottom 10 bits of an MDEC code are valid. */ - private static boolean validBottom(int iBottom10Bits) { - return iBottom10Bits >= -512 && iBottom10Bits <= 511; - } - - public MdecCode copy() { - return new MdecCode(_iTop6Bits, _iBottom10Bits); - } - - public String toString() { - if (isEOD()) - return "EOD"; - else - return "(" + _iTop6Bits + ", " + _iBottom10Bits + ")"; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 97 * hash + this._iTop6Bits; - hash = 97 * hash + this._iBottom10Bits; - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final MdecCode other = (MdecCode) obj; - return _iTop6Bits == other._iTop6Bits && - _iBottom10Bits == other._iBottom10Bits; - } - - } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStreamReader.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStreamReader.java index 2603ff0..b62ff66 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStreamReader.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/MdecInputStreamReader.java @@ -49,7 +49,7 @@ import jpsxdec.util.IO; /** Wraps an InputStream (or creates a FileInputStream) to read MDEC values from. */ -public class MdecInputStreamReader extends MdecInputStream { +public class MdecInputStreamReader implements MdecInputStream { private static final Logger LOG = Logger.getLogger(MdecInputStreamReader.class.getName()); @@ -100,11 +100,13 @@ public static void writeMdecDims(@Nonnull MdecInputStream mdecIn, * {@link MdecInputStream} to an {@link OutputStream}. * Errors are monitored, but does not throw exceptions on bad data * (although source and destination streams might). */ - public static void writeMdecBlocks(@Nonnull MdecInputStream mdecIn, + public static void writeMdecBlocks(@Nonnull MdecInputStream sourceMdecIn, @Nonnull OutputStream streamOut, int iBlockCount) throws MdecException.EndOfStream, MdecException.ReadCorruption, IOException { + Ac0Checker mdecIn = Ac0Checker.wrapWithChecker(sourceMdecIn, false); + MdecCode code = new MdecCode(); int iBlock = 0; while (iBlock < iBlockCount) { @@ -131,6 +133,8 @@ public static void writeMdecBlocks(@Nonnull MdecInputStream mdecIn, LOG.warning("non-EOD code found in at EOD position!"); IO.writeInt16LE(streamOut, code.toMdecWord()); } + + mdecIn.logIfAny0AcCoefficient(); } } diff --git a/jpsxdec/src/jpsxdec/psxvideo/mdec/tojpeg/Mdec2Jpeg.java b/jpsxdec/src/jpsxdec/psxvideo/mdec/tojpeg/Mdec2Jpeg.java index ce7860b..c5876ba 100644 --- a/jpsxdec/src/jpsxdec/psxvideo/mdec/tojpeg/Mdec2Jpeg.java +++ b/jpsxdec/src/jpsxdec/psxvideo/mdec/tojpeg/Mdec2Jpeg.java @@ -44,8 +44,11 @@ import java.util.logging.Logger; import jpsxdec.Version; import jpsxdec.i18n.I; -import jpsxdec.psxvideo.mdec.Ac0Cleaner; +import jpsxdec.psxvideo.mdec.Ac0Checker; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecBlock; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecContext; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; import jpsxdec.util.IO; @@ -123,9 +126,8 @@ public class Mdec2Jpeg { /** PSX standard quantization table in zig-zag order. */ private static final int[] PSX_QUANTIZATION_TABLE_ZIGZAG = new int[64]; static { - int[] aiNormalOrder = MdecInputStream.getDefaultPsxQuantMatrixCopy(); for (int i = 0; i < PSX_QUANTIZATION_TABLE_ZIGZAG.length; i++) { - int iQVal = aiNormalOrder[MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[i]]; + int iQVal = MdecInputStream.PSX_DEFAULT_QUANTIZATION_MATRIX[MdecInputStream.REVERSE_ZIG_ZAG_LOOKUP_LIST[i]]; PSX_QUANTIZATION_TABLE_ZIGZAG[i] = iQVal; if (i == 0) JPEG_QUANTIZATION_TABLE_ZIGZAG[i] = iQVal; @@ -159,9 +161,6 @@ else if ((iQVal % 8) == 0) /** End of image */ private static final int EOI = 0xD9; - private static final int PSX_CR_COMPONENT = 0; - private static final int PSX_CB_COMPONENT = 1; - private static final int PRECISION = 8; private static final int NUM_COMPONENTS = 3; @@ -177,6 +176,7 @@ else if ((iQVal % 8) == 0) private final int _iPixelWidth, _iPixelHeight; private final int _iMacBlockWidth, _iMacBlockHeight; + private final int _iTotalMacBlocks; /** Huffman tables as they will be written to the DHT block. */ private final HuffmanTable[] _aoDhtTables = { @@ -198,6 +198,7 @@ public Mdec2Jpeg(int iPixelWidth, int iPixelHeight) { _iPixelHeight = iPixelHeight; _iMacBlockWidth = Calc.macroblockDim(iPixelWidth); _iMacBlockHeight = Calc.macroblockDim(iPixelHeight); + _iTotalMacBlocks = _iMacBlockWidth * _iMacBlockHeight; _aoComponents[JPEG_Y_COMPONENT] = new Component(1, 0, 2, 2, 0, 0, _iMacBlockWidth, _iMacBlockHeight); _aoComponents[JPEG_CB_COMPONENT] = new Component(2, 0, 1, 1, 1, 1, _iMacBlockWidth, _iMacBlockHeight); @@ -216,98 +217,98 @@ public void readMdec(MdecInputStream mdecInStream) MdecException.EndOfStream { // while jpgs with AC=0 codes seem to be fine, still would like to avoid it - Ac0Cleaner cleanStream = (mdecInStream instanceof Ac0Cleaner) ? - (Ac0Cleaner)mdecInStream : new Ac0Cleaner(mdecInStream); + Ac0Checker cleanStream = Ac0Checker.wrapWithChecker(mdecInStream, true); - final MdecInputStream.MdecCode code = new MdecInputStream.MdecCode(); + final MdecCode code = new MdecCode(); for (Component comp : _aoComponents) { Arrays.fill(comp.DctCoffZZ, 0); comp.WriteIndex = 0; } - int iMacBlk = 0, iBlock = 0; - int iMacBlkX=-1, iMacBlkY=-1; + MdecContext context = new MdecContext(_iMacBlockHeight); // decode all the macro blocks of the image - for (iMacBlkX = 0; iMacBlkX < _iMacBlockWidth; iMacBlkX++) { - for (iMacBlkY = 0; iMacBlkY < _iMacBlockHeight; iMacBlkY++) { - - for (iBlock = 0; iBlock < 6; iBlock++) { - Component comp; - if (iBlock == PSX_CB_COMPONENT) - comp = _aoComponents[JPEG_CB_COMPONENT]; - else if (iBlock == PSX_CR_COMPONENT) - comp = _aoComponents[JPEG_CR_COMPONENT]; - else - comp = _aoComponents[JPEG_Y_COMPONENT]; - - cleanStream.readMdecCode(code); - - // normally would multiply by PSX_QUANTIZATION_TABLE_ZIGZAG[0] - // but JPEG_QUANTIZATION_TABLE_ZIGZAG[0] will take care of that - comp.DctCoffZZ[comp.WriteIndex] = code.getBottom10Bits(); - // note that so long as the MDEC codes are valid, - // the DC diff can never overflow the 11 bits it must fit in - // MDEC DC 10 bit: -512 to 511 - // max diff = 511 + 512 = 1023 - // 11 bits: +/- 2047 - - final int iCurrentBlockQscale = code.getTop6Bits(); - int iCurrentBlockVectorPosition = 0; - - while (!cleanStream.readMdecCode(code)) { - - //////////////////////////////////////////////////////// - iCurrentBlockVectorPosition += code.getTop6Bits() + 1; - - if (iCurrentBlockVectorPosition >= 64) { - throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_MB_XY_BLOCK( - iCurrentBlockVectorPosition, - iMacBlk, iMacBlkX, iMacBlkY, iBlock)); - } - - // Dequantize - int iJpegQScale = JPEG_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition]; - int iVal; - if (iJpegQScale == 1) { - iVal = (code.getBottom10Bits() - * PSX_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] - * iCurrentBlockQscale + 4) >> 3; - } else { - // normally would multiply by - // PSX_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] - // and divide by 8, but - // JPEG_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] - // will take care of that - iVal = code.getBottom10Bits() * iCurrentBlockQscale; - } - if (iVal < -1023 || iVal > 1023) { - // if this happens we would need to go back and - // increase values in the quantization table. - // however that would deviate even more from the - // origional frame quality, and be a huge pain - // to implement - // thankfully this doesn't seem to happen for - // normal (non-corrupted) frames - String msg = String.format( - "[JPG] Too much energy to encode %d in macroblock %d (%d, %d) block %d", - iVal, iMacBlk, iMacBlkX, iMacBlkY, iBlock); - LOG.log(Level.WARNING, msg); - throw new MdecException.TooMuchEnergy(msg); - } - comp.DctCoffZZ[comp.WriteIndex+iCurrentBlockVectorPosition] = iVal; + while (context.getTotalMacroBlocksRead() < _iTotalMacBlocks) { + + for (MdecBlock block : MdecBlock.list()) { + Component comp; + if (block == MdecBlock.Cr) + comp = _aoComponents[JPEG_CR_COMPONENT]; + else if (block == MdecBlock.Cb) + comp = _aoComponents[JPEG_CB_COMPONENT]; + else + comp = _aoComponents[JPEG_Y_COMPONENT]; + + cleanStream.readMdecCode(code); + + // normally would multiply by PSX_QUANTIZATION_TABLE_ZIGZAG[0] + // but JPEG_QUANTIZATION_TABLE_ZIGZAG[0] will take care of that + comp.DctCoffZZ[comp.WriteIndex] = code.getBottom10Bits(); + // note that so long as the MDEC codes are valid, + // the DC diff can never overflow the 11 bits it must fit in + // MDEC DC 10 bit: -512 to 511 + // max diff = 511 + 512 = 1023 + // 11 bits: +/- 2047 + + final int iCurrentBlockQscale = code.getTop6Bits(); + int iCurrentBlockVectorPosition = 0; + + while (!cleanStream.readMdecCode(code)) { + + //////////////////////////////////////////////////////// + iCurrentBlockVectorPosition += code.getTop6Bits() + 1; + + if (iCurrentBlockVectorPosition >= 64) { + MdecContext.MacroBlockPixel macBlkXY = context.getMacroBlockPixel(); + throw new MdecException.ReadCorruption(MdecException.RLC_OOB_IN_MB_XY_BLOCK( + iCurrentBlockVectorPosition, + context.getTotalMacroBlocksRead(), macBlkXY.x, macBlkXY.y, context.getCurrentBlock().ordinal())); + } - //////////////////////////////////////////////////////// + // Dequantize + int iJpegQScale = JPEG_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition]; + int iVal; + if (iJpegQScale == 1) { + iVal = (code.getBottom10Bits() + * PSX_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] + * iCurrentBlockQscale + 4) >> 3; + } else { + // normally would multiply by + // PSX_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] + // and divide by 8, but + // JPEG_QUANTIZATION_TABLE_ZIGZAG[iCurrentBlockVectorPosition] + // will take care of that + iVal = code.getBottom10Bits() * iCurrentBlockQscale; } + if (iVal < -1023 || iVal > 1023) { + // if this happens we would need to go back and + // increase values in the quantization table. + // however that would deviate even more from the + // origional frame quality, and be a huge pain + // to implement + // thankfully this doesn't seem to happen for + // normal (non-corrupted) frames + MdecContext.MacroBlockPixel macBlkXY = context.getMacroBlockPixel(); + String msg = String.format( + "[JPG] Too much energy to encode %d in macroblock %d (%d, %d) block %d", + iVal, context.getTotalMacroBlocksRead(), macBlkXY.x, macBlkXY.y, context.getCurrentBlock().ordinal()); + LOG.log(Level.WARNING, msg); + throw new MdecException.TooMuchEnergy(msg); + } + comp.DctCoffZZ[comp.WriteIndex+iCurrentBlockVectorPosition] = iVal; - comp.WriteIndex += 64; + //////////////////////////////////////////////////////// + context.nextCode(); } + context.nextCodeEndBlock(); - iMacBlk++; + comp.WriteIndex += 64; } + } + cleanStream.logIfAny0AcCoefficient(); } /** Writes the translated JPEG to the output. */ diff --git a/jpsxdec/src/jpsxdec/tim/CLUT.java b/jpsxdec/src/jpsxdec/tim/CLUT.java index 97a11fa..5d732ac 100644 --- a/jpsxdec/src/jpsxdec/tim/CLUT.java +++ b/jpsxdec/src/jpsxdec/tim/CLUT.java @@ -148,6 +148,7 @@ public int getWidth() { return bi; } + @Override public String toString() { return String.format( "%dx%d xy(%d, %d) Len:%d", diff --git a/jpsxdec/src/jpsxdec/tim/CreateTim.java b/jpsxdec/src/jpsxdec/tim/CreateTim.java index 1e38313..4271bc9 100644 --- a/jpsxdec/src/jpsxdec/tim/CreateTim.java +++ b/jpsxdec/src/jpsxdec/tim/CreateTim.java @@ -618,27 +618,7 @@ public PaletteMaker(@Nonnull int[] aiArgb, int iPaletteSize) { * @return Palette index or -1 if not found. */ public int getPixelPaletteIndex(int iPixelIndex) { short siTim16 = _asiTim16Image[iPixelIndex]; - return binarySearch(siTim16); - } - - /** Java 5 doesn't have binary search in a range. */ - private int binarySearch(short siKey) { - int iMin = 0; - int iMax = _iColorCount; - // continue searching while [min,max] is not empty - while (iMax >= iMin) { - int iMid = (iMin + iMax) >>> 1; - - // determine which subarray to search - if (_asiPalette[iMid] < siKey) // change min index to search upper subarray - iMin = iMid + 1; - else if (_asiPalette[iMid] > siKey) // change max index to search lower subarray - iMax = iMid - 1; - else // key found at index mid - return iMid; - } - // key not found - return -1; + return Arrays.binarySearch(_asiPalette, 0, _iColorCount, siTim16); } public @Nonnull CLUT makeClut(int iClutX, int iClutY) { diff --git a/jpsxdec/src/jpsxdec/tim/Tim.java b/jpsxdec/src/jpsxdec/tim/Tim.java index 676a605..814366c 100644 --- a/jpsxdec/src/jpsxdec/tim/Tim.java +++ b/jpsxdec/src/jpsxdec/tim/Tim.java @@ -429,6 +429,7 @@ public enum Mismatch { return null; } + @Override public String toString() { String s = String.format( "%dx%d %dbpp xy(%d, %d) WWidth:%d Len:%d", diff --git a/jpsxdec/src/jpsxdec/util/ByteArrayFPIS.java b/jpsxdec/src/jpsxdec/util/ByteArrayFPIS.java index 9f8a9f8..9e4b3be 100644 --- a/jpsxdec/src/jpsxdec/util/ByteArrayFPIS.java +++ b/jpsxdec/src/jpsxdec/util/ByteArrayFPIS.java @@ -109,6 +109,7 @@ public int getStreamByteSize() { return new ByteArrayFPIS(this); } + @Override public String toString() { return String.format("FP:%d Stream:%d/%d Buf:%d/%d", getFilePointer(), diff --git a/jpsxdec/src/jpsxdec/util/DemuxPushInputStream.java b/jpsxdec/src/jpsxdec/util/DemuxPushInputStream.java index d8d7aa3..8a5aff1 100644 --- a/jpsxdec/src/jpsxdec/util/DemuxPushInputStream.java +++ b/jpsxdec/src/jpsxdec/util/DemuxPushInputStream.java @@ -62,7 +62,7 @@ public class DemuxPushInputStream extends InputStre private static final Logger LOG = Logger.getLogger(DemuxPushInputStream.class.getName()); - /** Thrown when the stream is still open and the available data has + /** Thrown when the stream is still open but the available data has * been exhausted. */ public static class NeedsMoreData extends IOException { @@ -206,7 +206,7 @@ public boolean isEof() { return _readStream.isEof(); } - public @CheckForNull T getCurrentPiece() { + public @Nonnull T getCurrentPiece() { return _readStream.getCurrentPiece(); } @@ -353,7 +353,7 @@ private static class CopyablePieceSequenceStream ex public CopyablePieceSequenceStream(@Nonnull BufferedPushIterator.Iter pieceInterator) { _pieceIterator = pieceInterator; - _currentPieceStream = new PieceInputStream(_pieceIterator.next()); + _currentPieceStream = new PieceInputStream(_pieceIterator.next()); // find out how much is initially available _iAvailable = _currentPieceStream.available(); BufferedPushIterator.Iter it = _pieceIterator.copy(); diff --git a/jpsxdec/src/jpsxdec/util/DemuxedData.java b/jpsxdec/src/jpsxdec/util/DemuxedData.java index 0303564..7056d9f 100644 --- a/jpsxdec/src/jpsxdec/util/DemuxedData.java +++ b/jpsxdec/src/jpsxdec/util/DemuxedData.java @@ -139,6 +139,7 @@ public int getEndSector() { /** Returns in the order of data, not necessarily in the order of sectors. */ // TODO find some way to remove iterator here // it's used for printing the sectors and replacing them mainly I think + @SuppressWarnings("unchecked") public @Nonnull Iterator iterator() { // this cast is particularly interesting because there's really no way around it // but logically it is perfectly safe diff --git a/jpsxdec/src/jpsxdec/util/Fraction.java b/jpsxdec/src/jpsxdec/util/Fraction.java index 1fc9c8f..79ccf36 100644 --- a/jpsxdec/src/jpsxdec/util/Fraction.java +++ b/jpsxdec/src/jpsxdec/util/Fraction.java @@ -55,6 +55,7 @@ public Fraction(@Nonnull Fraction f) { denominator_ = f.getDenominator(); } + @Override public String toString() { if (getDenominator() == 1) return String.format("%d", getNumerator()); @@ -234,6 +235,7 @@ public int compareTo(long n) { return (l < r)? -1 : ((l == r)? 0: 1); } + @Override public boolean equals(Object other) { return compareTo((Fraction)other) == 0; } @@ -242,6 +244,7 @@ public boolean equals(long n) { return compareTo(n) == 0; } + @Override public int hashCode() { return (int) (numerator_ ^ denominator_); } diff --git a/jpsxdec/src/jpsxdec/util/IO.java b/jpsxdec/src/jpsxdec/util/IO.java index 63e475f..11bbca2 100644 --- a/jpsxdec/src/jpsxdec/util/IO.java +++ b/jpsxdec/src/jpsxdec/util/IO.java @@ -46,8 +46,6 @@ import jpsxdec.i18n.I; import jpsxdec.i18n.exception.LocalizedFileNotFoundException; -// TODO: unify write(types) - /** Additional functions for reading, writing, and whatnot. */ public final class IO { @@ -154,6 +152,14 @@ public static int UInt16LE(int b1, int b2) { //== 16-bit == big-endian == signed == read ================================ + public static int readSInt16BE(@Nonnull InputStream stream) throws EOFException, IOException { + int b1, b2; + if ((b1 = stream.read()) < 0 || + (b2 = stream.read()) < 0) + throw new EOFException(); + return SInt16BE(b1, b2); + } + public static short readSInt16BE(@Nonnull byte[] ab, int i) { int b1 = ab[i ] & 0xff; int b2 = ab[i+1] & 0xff; @@ -654,16 +660,6 @@ public static void writeIStoOS(@Nonnull InputStream is, @Nonnull OutputStream os //== other ================================================================= - public static @Nonnull int[] readBEIntArray(@Nonnull InputStream stream, int iCount) - throws EOFException, IOException - { - int[] ai = new int[iCount]; - for (int i = 0; i < ai.length; i++) { - ai[i] = readSInt32BE(stream); - } - return ai; - } - /** Creates the directory or throws {@link LocalizedFileNotFoundException} * if anything goes wrong. * If dir is null does nothing. If the directory already exists, nothing diff --git a/jpsxdec/src/jpsxdec/util/Misc.java b/jpsxdec/src/jpsxdec/util/Misc.java index 5de8cb3..13a9554 100644 --- a/jpsxdec/src/jpsxdec/util/Misc.java +++ b/jpsxdec/src/jpsxdec/util/Misc.java @@ -45,6 +45,8 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -60,7 +62,7 @@ public final class Misc { * Every implementation of the Java platform is required to support US-ASCII. * @see Charset */ - public static byte[] stringToAscii(@Nonnull String string) { + public static @Nonnull byte[] stringToAscii(@Nonnull String string) { try { return string.getBytes("US-ASCII"); } catch (UnsupportedEncodingException ex) { @@ -68,10 +70,10 @@ public static byte[] stringToAscii(@Nonnull String string) { } } - public static String asciiToString(@Nonnull byte[] ascii) { + public static @Nonnull String asciiToString(@Nonnull byte[] ascii) { return asciiToString(ascii, 0, ascii.length); } - public static String asciiToString(@Nonnull byte[] ascii, int iOffset, int iLength) { + public static @Nonnull String asciiToString(@Nonnull byte[] ascii, int iOffset, int iLength) { try { return new String(ascii, iOffset, iLength, "US-ASCII"); } catch (UnsupportedEncodingException ex) { @@ -79,6 +81,13 @@ public static String asciiToString(@Nonnull byte[] ascii, int iOffset, int iLeng } } + /** Makes a date X number of seconds past the year 0. */ + public static @Nonnull Date dateFromSeconds(int iSeconds) { + Calendar c = Calendar.getInstance(); + c.set(0, 0, 0, 0, 0, iSeconds); + return c.getTime(); + } + /** Returns an array of just the matching groups. * @return null if failed to match */ public static @CheckForNull String[] regex(@Nonnull String regex, @Nonnull String s) { @@ -161,21 +170,6 @@ public static boolean objectEquals(@CheckForNull Object o1, @CheckForNull Object return new String(ac); } - /** Manual implementation of the Java 6 Array.copyOfRange function. - * Borrowed from some older Apache code. */ - public static @Nonnull byte[] copyOfRange(@Nonnull byte[] original, int from, int to) { - int newLength = to - from; - if (newLength < 0) - throw new IllegalArgumentException(from + " > " + to); - - byte[] arr = new byte[newLength]; - int ceil = original.length-from; - int len = (ceil < newLength) ? ceil : newLength; - System.arraycopy(original, from, arr, 0, len); - - return arr; - } - /** Removes the extension from the given file name/path. */ public static @Nonnull String removeExt(@Nonnull String sFileName) { int i = sFileName.lastIndexOf('.'); diff --git a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriter.java b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriter.java index 11b99e2..2f0bbae 100644 --- a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriter.java +++ b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriter.java @@ -323,7 +323,7 @@ protected AviWriter(final @Nonnull File outputfile, LIST_movi = new Chunk(_aviFile, "LIST", "movi"); } catch (IOException ex) { - IO.closeSilently(_aviFile, Logger.getLogger(AviWriter.class.getName())); + closeSilentlyDueToError(); throw ex; } // now we're ready to start accepting video/audio data @@ -332,6 +332,10 @@ protected AviWriter(final @Nonnull File outputfile, _indexList = new ArrayList(); } + final protected void closeSilentlyDueToError() { + IO.closeSilently(_aviFile, Logger.getLogger(AviWriter.class.getName())); + } + // ------------------------------------------------------------------------- // -- Writing functions ---------------------------------------------------- // ------------------------------------------------------------------------- diff --git a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterMJPG.java b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterMJPG.java index d0c9467..601fd4b 100644 --- a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterMJPG.java +++ b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterMJPG.java @@ -140,8 +140,10 @@ public AviWriterMJPG(final @Nonnull File outputfile, { super(outputfile, iWidth, iHeight, lngFrames, lngPerSecond, audioFormat, true, "MJPG", AVIstruct.string2int("MJPG")); - if (!CAN_ENCODE_JPEG) + if (!CAN_ENCODE_JPEG) { + closeSilentlyDueToError(); throw new UnsupportedOperationException("Unable to create 'jpeg' images on this platform."); + } Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); _imgWriter = iter.next(); diff --git a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterYV12.java b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterYV12.java index 50929be..94e1448 100644 --- a/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterYV12.java +++ b/jpsxdec/src/jpsxdec/util/aviwriter/AviWriterYV12.java @@ -74,8 +74,10 @@ public AviWriterYV12(final @Nonnull File outFile, super(outFile, iWidth, iHeight, lngFrames, lngPerSecond, audioFormat, false, "YV12", AVIstruct.string2int("YV12")); - if (((iWidth | iHeight) & 1) != 0) + if (((iWidth | iHeight) & 1) != 0) { + closeSilentlyDueToError(); throw new IllegalArgumentException("Dimensions must be divisible by 2"); + } _iFrameYByteSize = iWidth * iHeight; _iFrameCByteSize = iWidth * iHeight / 4; diff --git a/jpsxdec/src/jpsxdec/util/player/AudioPlayer.java b/jpsxdec/src/jpsxdec/util/player/AudioPlayer.java index 196d342..ae0792c 100644 --- a/jpsxdec/src/jpsxdec/util/player/AudioPlayer.java +++ b/jpsxdec/src/jpsxdec/util/player/AudioPlayer.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -37,49 +37,111 @@ package jpsxdec.util.player; -import java.util.logging.Level; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.util.logging.Logger; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; -import jpsxdec.util.player.VideoPlayer.VideoFrame; +import jpsxdec.util.IO; -/** Manages writing audio data to the final {@link SourceDataLine}. */ -class AudioPlayer implements IVideoTimer { +/** Manages writing audio data to the final {@link SourceDataLine}. + * A buffer sits between the data written to this class and the actual DataLine + * because DataLine seems to have a very small buffer itself. + * A thread manages copying the buffer into the DataLine. + * + * This also extends VideoTimer that manages the video playback using + * the audio timer. It conveniently uses the DataLine's listener to + * fire events. */ +class AudioPlayer extends VideoTimer implements Runnable, LineListener { private static final Logger LOG = Logger.getLogger(AudioPlayer.class.getName()); private static final boolean DEBUG = false; - private static final int SECONDS_OF_BUFFER = 5; - private static final int FRAME_DELAY_FUDGE_TIME = 50; + private static final boolean IS_WINDOWS; + static { + IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("win"); + } - @CheckForNull - private SourceDataLine _dataLine; - private final PlayingState _state = new PlayingState(PlayingState.State.STOPPED); + private static final long NANOS_PER_SECOND = 1000000000; + + /** On Ubuntu it seems a big buffer causes issues when video is paused. + * The play time drifts a lot when paused and buffering, so when + * unpaused the video hangs a lot. Smaller buffer reduces that effect. */ + private static final double SECONDS_OF_BUFFER_NON_WINDOWS = 0.1; + /** On windows it seems a small buffer causes stuttering, so use a big one. */ + private static final double SECONDS_OF_BUFFER_WINDOWS = 5; @Nonnull private final AudioFormat _format; - private final double _dblTimeConvert; @Nonnull - private final PlayController _controller; + private final PipedInputStream _pipedInputStream; + @Nonnull + private final PipedOutputStream _pipedOutputStream; - public AudioPlayer(@Nonnull AudioFormat format, @Nonnull PlayController controller) { + private final double _dblSamplesPerNano; + private final int _iCopyBufferSize; + + @Nonnull + private final Thread _thread; + + private SourceDataLine _dataLine; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Experiment with using the system clock and intermittently sync with the audio time + private boolean _blnUseAudioAndSystemClockTogether = false; + private long _lngStartTime = -1; + private long _lngLastSync = -1; + private static final long RESYNC_EVERY_NANOS = NANOS_PER_SECOND / 2; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public AudioPlayer(@Nonnull AudioFormat format) { _format = format; - _controller = controller; - _dblTimeConvert = 1000000000. / _format.getSampleRate(); + + _dblSamplesPerNano = (double)NANOS_PER_SECOND / _format.getSampleRate(); + + int iPipeSize = _format.getFrameSize() * Math.round(_format.getSampleRate()) * 5; + _pipedInputStream = new PipedInputStream(iPipeSize); + try { + _pipedOutputStream = new PipedOutputStream(_pipedInputStream); + } catch (IOException ex) { + throw new RuntimeException("Should not happen", ex); + } + _iCopyBufferSize = (int) (format.getFrameSize() * format.getSampleRate() * 5); + + _thread = new Thread(this, getClass().getName()); } - private static @Nonnull SourceDataLine createOpenLine(@Nonnull AudioFormat format) + + public synchronized void initPaused() throws PlayerException { + if (_dataLine != null) + throw new IllegalStateException(); + try { + _dataLine = createOpenLine(_format); + // Important note about dataline listeners: + // The events are not triggered when the is a buffer under-run + // although the docs kinda sugget that they are + _dataLine.addLineListener(this); + _thread.start(); + } catch (LineUnavailableException ex) { + throw new PlayerException(ex); + } + } + + private static @Nonnull SourceDataLine createOpenLine(@Nonnull AudioFormat format) throws LineUnavailableException { - + final boolean blnUseDefault = true; - SourceDataLine dataLine; + SourceDataLine dataLine = null; if (blnUseDefault) { dataLine = AudioSystem.getSourceDataLine(format); } else { @@ -87,190 +149,190 @@ public AudioPlayer(@Nonnull AudioFormat format, @Nonnull PlayController controll Mixer.Info[] aoMixerInfos = AudioSystem.getMixerInfo(); System.out.println("[AudioPlayer] Available mixers:"); for (Mixer.Info mixerInfo : aoMixerInfos) { - System.out.println("[AudioPlayer] " + mixerInfo.getName()); + System.out.println("[AudioPlayer] " + mixerInfo.getName() + " " + mixerInfo.getDescription()); } - Mixer mixer = AudioSystem.getMixer(aoMixerInfos[0]); - dataLine = (SourceDataLine)mixer.getLine(info); - } - /* Start and Stop events are useless to me because they do not - * occur when playing is stopped due to no data in the buffer. - dataLine.addLineListener(new LineListener() { - public void update(LineEvent event) { - System.out.println(event); + for (Mixer.Info mixerInfo : aoMixerInfos) { + Mixer mixer = AudioSystem.getMixer(mixerInfo); + try { + System.out.println("[AudioPlayer] Trying " + mixerInfo.getName()); + if (mixerInfo.getName().contains("default")) + continue; + dataLine = (SourceDataLine)mixer.getLine(info); + break; + } catch (LineUnavailableException ex) { + ex.printStackTrace(); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } } - }); - */ + } - dataLine.open(format, format.getFrameSize() * (int)format.getSampleRate() * SECONDS_OF_BUFFER); + double dblSeconds; + if (IS_WINDOWS) + dblSeconds = SECONDS_OF_BUFFER_WINDOWS; + else + dblSeconds = SECONDS_OF_BUFFER_NON_WINDOWS; + + int iRequestedBufferSize = (int) (format.getFrameSize() * format.getSampleRate() * dblSeconds); + dataLine.open(format, iRequestedBufferSize); + // for some reason on Ubuntu the audio starts as soon as data is fed to it + // no matter how many times .stop() is called + // somehow calling start() then stop() gets around it + dataLine.start(); + dataLine.stop(); + int iActualBufferSize = dataLine.getBufferSize(); + System.out.println("Dataline requested buffer size " + iRequestedBufferSize + " actual buffer size " + iActualBufferSize); return dataLine; } - /** Will block until all audio was written or there is a player state change. */ - public void write(@Nonnull byte[] abData, int iStart, int iLength) { + public void run() { try { - int iTotalWritten = 0; - while (iTotalWritten < iLength) { - if (_state.get() == PlayingState.State.STOPPED) { + byte[] abCopyBuffer = new byte[_iCopyBufferSize]; + int iBytesToWrite = 0, iBytesWritten = 0; + while (true) { + if (!_dataLine.isOpen()) { break; } - int iWritten = _dataLine.write(abData, iTotalWritten, iLength - iTotalWritten); - iTotalWritten += iWritten; - if (iTotalWritten < iLength) { - synchronized (_state) { - if (_state.get() == PlayingState.State.PAUSED) { - _state.waitForChange(); - } else { - System.out.println("[AudioPlayer] Only " + iWritten + + + if (iBytesWritten < iBytesToWrite) { + int iToWrite = iBytesToWrite - iBytesWritten; + int iFrameRemainder = iToWrite % _format.getFrameSize(); + if (iFrameRemainder > 0) + iToWrite -= iFrameRemainder; + if (DEBUG) System.out.println("Writing " + iToWrite + " bytes of audio"); + int iWrorte = _dataLine.write(abCopyBuffer, iBytesWritten, iToWrite); + if (DEBUG) System.out.println("Actually wrote " + iWrorte + " bytes of audio"); + iBytesWritten += iWrorte; + if (iWrorte < iToWrite) { + boolean blnDueToPause = false; + // possible race condition here if the play state changed between writting audio data and checking state + // but that shouln't cause any problems except trigger the message below + synchronized (this) { + if (isPaused()) { + this.wait(); + blnDueToPause = true; + } + } + if (!blnDueToPause) { + // special to note that the dataline will continually reject audio while it is paused + // so no need to log in that case, just wait + System.out.println("[AudioPlayer] Only " + iWrorte + " bytes of audio was written. Progress: " + - iTotalWritten + "/" + iLength); + iBytesWritten + "/" + iBytesToWrite); } - } - } - } - } catch (InterruptedException ex) { - LOG.log(Level.SEVERE, null, ex); - ex.printStackTrace(); - } - } - - /** Buffer of zeros for writing lots of zeros. */ - @CheckForNull - private byte[] _abZeroBuff; - public void writeSilence(long lngSamples) { - try { - if (_abZeroBuff == null) { - _abZeroBuff = new byte[_format.getFrameSize() * 2048]; - } - final long lngBytesToWrite = lngSamples * _format.getFrameSize(); - long lngBytesLeft = lngBytesToWrite; - while (lngBytesLeft > 0) { - if (_state.get() == PlayingState.State.STOPPED) { - break; - } - int iWritten = _dataLine.write(_abZeroBuff, 0, (int)Math.min(lngBytesLeft, _abZeroBuff.length)); - lngBytesLeft -= iWritten; - if (lngBytesLeft > 0) { - synchronized (_state) { - if (_state.get() == PlayingState.State.PAUSED) { - _state.waitForChange(); - } else { - System.out.println("[AudioPlayer] Only " + iWritten + - " bytes of silence was written. Progress: " + - (lngBytesToWrite - lngBytesLeft) + "/" + lngBytesToWrite); - } + } + } else { + iBytesToWrite = _pipedInputStream.read(abCopyBuffer); + if (DEBUG) System.out.println("Got " + iBytesToWrite + " byts of audio"); + iBytesWritten = 0; + if (iBytesToWrite < 0) { + break; } } } + } catch (IOException ex) { + // this hopefully only happens because the reader was forcefully closed + System.out.println("Audio player IOException stop: " + ex.getMessage()); + _dataLine.close(); + super.terminate(); + return; } catch (InterruptedException ex) { - LOG.log(Level.SEVERE, null, ex); ex.printStackTrace(); + _dataLine.close(); + super.terminate(); + return; } - } - - public void drainAndClose() { _dataLine.drain(); - stop(); - } - - public boolean isDone() { - return _state.get() == PlayingState.State.STOPPED; - } - - public void startPaused() throws LineUnavailableException { - synchronized (_state) { - if (_state.get() == PlayingState.State.STOPPED) { - _state.set(PlayingState.State.PAUSED); - _dataLine = createOpenLine(_format); - } - } + _dataLine.close(); + super.terminate(); } - public void stop() { - synchronized (_state) { - switch (_state.get()) { - case PLAYING: case PAUSED: - _dataLine.close(); - _state.set(PlayingState.State.STOPPED); - _controller.notifyDonePlaying(); - } - } + + public @Nonnull OutputStream getOutputStream() { + return _pipedOutputStream; } - public void pause() { - synchronized (_state) { - if (_state.get() == PlayingState.State.PLAYING) { - _state.set(PlayingState.State.PAUSED); - _dataLine.stop(); - } + @Override + public synchronized void go() { + // synchronized since this is 2 operations + // and we don't want the player state to change in the middle + if (_dataLine != null) { + long lngNow = System.nanoTime(); + _lngLastSync = lngNow; + _lngStartTime = lngNow - (long)(_dataLine.getLongFramePosition() * _dblSamplesPerNano); + _dataLine.start(); } + super.go(); } - public void unpause() { - synchronized (_state) { - if (_state.get() == PlayingState.State.PAUSED) { - _state.set(PlayingState.State.PLAYING); - _dataLine.start(); - } + @Override + public synchronized void pause() { + // synchronized to keep the dataline state in sync with the VideoTimer state + if (_dataLine != null) { + //_dataLine.flush(); + _dataLine.stop(); } + super.pause(); } - public long getNanoPlayTime() { - return (long)(_dataLine.getLongFramePosition() * _dblTimeConvert); + public void finish() { + // it looks like it's thread-safe to close the stream + IO.closeSilently(_pipedOutputStream, LOG); } - public @Nonnull Object getSyncObject() { - return _state; + @Override + public void videoDone() { + // it doesn't matter if the video is done, we continue playing audio } - public boolean shouldBeProcessed(long lngPresentationTime) { - synchronized (_state) { - switch (_state.get()) { - case PAUSED: - case PLAYING: - long lngPlayTime = getNanoPlayTime(); - if (DEBUG) System.out.println("[AudioPlayer] Play time = " + lngPlayTime + " vs. Pres time = " + lngPresentationTime); - return lngPresentationTime >= lngPlayTime; - case STOPPED: - return false; - default: - throw new RuntimeException("Should never happen"); - } + @Override + public synchronized void terminate() { + // it looks like it's thread-safe to close the streams + // synchronized to keep the dataline state in sync with the VideoTimer state + // Make sure to close the player first! + // Otherwise possible deadlock if the thread is stuck writing when this is called + if (_dataLine != null) { + _dataLine.stop(); + _dataLine.close(); } + IO.closeSilently(_pipedOutputStream, LOG); + IO.closeSilently(_pipedInputStream, LOG); + super.terminate(); } - public boolean waitToPresent(@Nonnull VideoFrame frame) { - try { - synchronized (_state) { - while (true) { - switch (_state.get()) { - case STOPPED: - return false; - case PAUSED: - if (DEBUG) System.out.println("[AudioPlayer] AudioPlayer timer, waiting to present"); - _state.waitForChange(); - if (DEBUG) System.out.println("[AudioPlayer] AudioPlayer timer, not waiting anymore"); - break; // loop again to see the new state - case PLAYING: - long lngPos = getNanoPlayTime(); - long lngSleepTime; - if ((lngSleepTime = frame.PresentationTime - lngPos) > FRAME_DELAY_FUDGE_TIME) { - lngSleepTime -= FRAME_DELAY_FUDGE_TIME; - _state.wait(lngSleepTime / 1000000, (int)(lngSleepTime % 1000000)); - break; // loop once more to see if the state changed - } - return true; - default: - throw new RuntimeException("Should never happen"); - } - } + public synchronized long getNanoTime() { + if (!_blnUseAudioAndSystemClockTogether || isPaused() || isTerminated()) { + return (long)(_dataLine.getLongFramePosition() * _dblSamplesPerNano); + } else { + long lngNow = System.nanoTime(); + if (lngNow - _lngLastSync > RESYNC_EVERY_NANOS) { + long lngPlayTime = (long)(_dataLine.getLongFramePosition() * _dblSamplesPerNano); + long lngOldStartTime = _lngStartTime; + _lngStartTime = lngNow - lngPlayTime; + _lngLastSync = lngNow; + System.out.println("Resyncing from " + lngOldStartTime + " to " + _lngStartTime + " (" + ((_lngStartTime - lngOldStartTime) / (double)NANOS_PER_SECOND) + ")"); + return lngPlayTime; + } else { + return lngNow - _lngStartTime; } - } catch (Throwable ex) { - LOG.log(Level.SEVERE, null, ex); - ex.printStackTrace(); - return false; } } + /** Translate an audio event to this player's event. */ + public void update(LineEvent event) { + LineEvent.Type type = event.getType(); + PlayController.Event playerEvent; + if (type == LineEvent.Type.CLOSE) + playerEvent = PlayController.Event.End; + else if (type == LineEvent.Type.START) + playerEvent = PlayController.Event.Play; + else if (type == LineEvent.Type.STOP) + playerEvent = PlayController.Event.Pause; + else + return; + + fire(playerEvent); + } } diff --git a/jpsxdec/src/jpsxdec/util/player/AudioVideoReader.java b/jpsxdec/src/jpsxdec/util/player/AudioVideoReader.java deleted file mode 100644 index d93b03f..0000000 --- a/jpsxdec/src/jpsxdec/util/player/AudioVideoReader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 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.util.player; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; -import javax.sound.sampled.AudioFormat; - -/** User created object to do the actual reading of audio or video chunks. - * Also indicates the format of the chucks being read. */ -public abstract class AudioVideoReader implements Runnable { - - @CheckForNull - private AudioPlayer _audioPlayer; - @CheckForNull - private VideoProcessor _videoProcessor; - private int _iProgress = -1; - - @CheckForNull - private Thread _thread; - - /** Only stopped or playing. */ - private final PlayingState _state = new PlayingState(PlayingState.State.STOPPED); - - void init(@CheckForNull AudioPlayer audPlay, @CheckForNull VideoProcessor vidProc) { - _audioPlayer = audPlay; - _videoProcessor = vidProc; - } - - public void writeAudio(@Nonnull byte[] abData, int iStart, int iLength) { - _audioPlayer.write(abData, iStart, iLength); - } - - public void writeSilence(long lngSamples) { - _audioPlayer.writeSilence(lngSamples); - } - - public void writeFrame(@Nonnull IDecodableFrame frame) { - _videoProcessor.writeFrame(frame); - } - - /** @param iProgress 0 to 100. */ - public void setReadProgress(int iProgress) { - _iProgress = iProgress; - } - - protected boolean stillPlaying() { - return _state.get() != PlayingState.State.STOPPED; - } - - final public void run() { - try { - demuxThread(); - } finally { - System.out.println("[AudioVideoReader] At end, telling everyone to stop when empty"); - if (_videoProcessor != null) - _videoProcessor.writerClose(); - if (_audioPlayer != null) - _audioPlayer.drainAndClose(); - _state.set(PlayingState.State.STOPPED); - } - } - - void startBuffering() { - synchronized (_state) { - if (_state.get() == PlayingState.State.STOPPED) { - _state.set(PlayingState.State.PLAYING); - _thread = new Thread(this, getClass().getName()); - _thread.start(); - } - } - } - - void stop() { - synchronized (_state) { - if (_state.get() == PlayingState.State.PLAYING) { - _state.set(PlayingState.State.STOPPED); - } - } - } - - abstract protected void demuxThread(); - /** Return null if no audio. */ - abstract public @CheckForNull AudioFormat getAudioFormat(); - abstract public boolean hasVideo(); - abstract public int getVideoWidth(); - abstract public int getVideoHeight(); - abstract public double getDuration(); -} diff --git a/jpsxdec/src/jpsxdec/util/player/ClosableArrayBlockingQueue.java b/jpsxdec/src/jpsxdec/util/player/ClosableArrayBlockingQueue.java new file mode 100644 index 0000000..974a58c --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/ClosableArrayBlockingQueue.java @@ -0,0 +1,261 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * Normal array blocking queue but will the ability to 'close' it and + * 'poison' it. closeNow() will immediately unblock any waiting threads. + * closeWhenEmpty() will unblock any writers and will reject anything + * else being added. When the queue is empty, it will be flagged as closed. +*/ +public class ClosableArrayBlockingQueue { + + private static final Object POISON_PILL = new Object(); + + @Nonnull + private final Queue _queue; + private final int _iCapacity; + + @Nonnull + private final ReentrantLock _lock; + @Nonnull + private final Condition _notEmpty; + @Nonnull + private final Condition _notFull; + private boolean _blnIsClosed = false; + private boolean _blnIsPoisoned = false; + + /** + * The last thread used to add an entry to the queue. + * {@link #take()} will check this periodically to see if the adding thread died. + * Hopefully will minimize the likelihood of {@link #take()} blocking a thread forever. + * Idea borrowed from PipedInputStream. + */ + @CheckForNull + private Thread _addingThread; + @CheckForNull + private Thread _takingThread; + + public ClosableArrayBlockingQueue(int iCapacity) { + _iCapacity = iCapacity; + _queue = new ArrayDeque(); + _lock = new ReentrantLock(); + _notEmpty = _lock.newCondition(); + _notFull = _lock.newCondition(); + } + + /** + * Blocks only if the queue is full, and the queue is not closed or poisoned. + * @return if the queue is still open and not poisoned (false if not) + * @throws NullPointerException if the argument is null + */ + public boolean add(@Nonnull T entry) throws InterruptedException { + return innerAdd(entry, false); + } + + /** + * Adds to the queue, but instead of blocking when the size limit is + * reached, close the queue and blow up with an exception. + * @return if the queue is still open and not poisoned (false if not) + * @throws NullPointerException if the argument is null + * @throws IllegalStateException if the queue is full + */ + public boolean addWithCapacityCheck(@Nonnull T entry) { + try { + return innerAdd(entry, true); + } catch (InterruptedException ex) { + throw new RuntimeException("should not happen", ex); + } + } + + private boolean innerAdd(@Nonnull Object entry, boolean blnFailIfTooBig) throws InterruptedException { + // the queue doesn't allow null entries + // plus we use null to indicate take() should not wait + if (entry == null) + throw new NullPointerException(); + + _lock.lock(); + try { + // grab the current thread for take() to check + _addingThread = Thread.currentThread(); + + // always loop when dealing with thread notifications + while (true) { + + // easy check, is this closed or poisoned? + if (_blnIsClosed || _blnIsPoisoned) { + return false; + + } else { + int iQueueSize = _queue.size(); + // next easy check, is there room to add? + if (iQueueSize < _iCapacity) { + _queue.add(entry); + _notEmpty.signalAll(); + return true; + } else { + + if (blnFailIfTooBig) { + closeNow(); + throw new IllegalStateException("Queue is too big: " + iQueueSize + "/" + _iCapacity); + } + + // sanity check + checkOtherThreadBeforeWaiting(_takingThread); + + // all else failed, now wait for room in the queue + // but don't wait forever, and do all checks again in case the other thread died + boolean dontCareWhy = _notEmpty.await(1, TimeUnit.SECONDS); + } + } + } + + } finally { + _lock.unlock(); + } + } + + /** + * Blocks until: + * - something is available + * - the queues is closed + * @return null if the queue is closed or the poison pill has been encountered, thus closing the queue. + */ + @SuppressWarnings("unchecked") + public @CheckForNull T take() throws InterruptedException { + _lock.lock(); + try { + + // grab the current thread for add() to check + _takingThread = Thread.currentThread(); + + // always loop when dealing with thread notifications + while (true) { + + // easy check, is this closed? + if (_blnIsClosed) { + return null; + } else { + + // next easy check, something in the queue? + if (!_queue.isEmpty()) { + Object entry = _queue.remove(); + _notFull.signalAll(); + if (entry == POISON_PILL) { + closeNow(); + return null; + } else { + return (T) entry; + } + } else { + + checkOtherThreadBeforeWaiting(_addingThread); + + // all else failed, now wait for something in the queue + // but don't wait forever, and do all checks again in case the other thread died + boolean dontCareWhy = _notEmpty.await(1, TimeUnit.SECONDS); + } + } + } + } finally { + _lock.unlock(); + } + } + + private static void checkOtherThreadBeforeWaiting(@CheckForNull Thread otherThread) { + // now a thread sanity check to avoid deadlocks + if (otherThread != null) { + if (!otherThread.isAlive()) { + throw new IllegalStateException(otherThread.getName() + " thread is dead, throwing exception to make sure this thread (" + + Thread.currentThread().getName() + ") won't block forever"); + } else if (otherThread == Thread.currentThread()) { + throw new IllegalStateException("The last thread to add entries was THIS thread (" + + Thread.currentThread().getName() + + "), throwing exception to make sure this won't block forever"); + } + } + } + + /** + * Closes the queue and returns immediately. + * Calling multiple times will only notify the threads again. + */ + public void closeNow() { + _lock.lock(); + try { + _blnIsClosed = true; + _queue.clear(); + _notEmpty.signalAll(); + _notFull.signalAll(); + } finally { + _lock.unlock(); + } + } + + /** + * Blocks adding elements to this queue. + * Calling multiple times will only notify the threads again. + */ + public void closeWhenEmpty() { + _lock.lock(); + try { + if (_blnIsClosed) + return; + if (_blnIsPoisoned) + return; + _queue.add(POISON_PILL); // add at end, even if it makes the queue > max + _notEmpty.signalAll(); + _blnIsPoisoned = true; + } finally { + _lock.unlock(); + } + } + + public boolean isClosed() { + return _blnIsClosed; + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/DecodableFrame.java b/jpsxdec/src/jpsxdec/util/player/DecodableFrame.java new file mode 100644 index 0000000..21cce39 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/DecodableFrame.java @@ -0,0 +1,55 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.Nonnull; + +class DecodableFrame { + + @Nonnull + public final T frame; + /** Returns the time the frame should be displayed, in nano-seconds + * from the beginning of the movie. */ + public final long lngPresentationNanos; + + public DecodableFrame(@Nonnull T frame, long lngPresentationNanos) { + this.frame = frame; + this.lngPresentationNanos = lngPresentationNanos; + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/PlayingState.java b/jpsxdec/src/jpsxdec/util/player/DecodedVideoFrame.java similarity index 71% rename from jpsxdec/src/jpsxdec/util/player/PlayingState.java rename to jpsxdec/src/jpsxdec/util/player/DecodedVideoFrame.java index d22d341..acfb622 100644 --- a/jpsxdec/src/jpsxdec/util/player/PlayingState.java +++ b/jpsxdec/src/jpsxdec/util/player/DecodedVideoFrame.java @@ -37,39 +37,26 @@ package jpsxdec.util.player; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Transparency; +import java.awt.image.BufferedImage; import javax.annotation.Nonnull; - -class PlayingState { - - private final static boolean DEBUG = false; - - public static enum State { PLAYING, PAUSED, STOPPED } - +/** Private class to hold a {@link BufferedImage} where the decoded frame + * is written and its presentation time. */ +class DecodedVideoFrame { @Nonnull - private volatile State _state; - - public PlayingState(@Nonnull State state) { - _state = state; - } - - public synchronized @Nonnull State get() { - return _state; - } - - public synchronized void set(@Nonnull State val) { - if (_state != val) { - _state = val; - if (DEBUG) System.out.println(Thread.currentThread().getName() + " State changed to " + val + ", notifying all"); - notifyAll(); - } - } - - public synchronized void waitForChange() throws InterruptedException { - State current = _state; - while (current == _state) { - wait(); - } + public final BufferedImage image; + public long lngPresentationNanos = -1; + + public DecodedVideoFrame(int iWidth, int iHeight) { + // TODO experiment with how much time it takes to create this + // and if there are other, faster ways to get decoded video + // data to the screen + GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + image = gc.createCompatibleImage(iWidth, iHeight, Transparency.OPAQUE); } } + diff --git a/jpsxdec/src/jpsxdec/util/player/IFrameProcessor.java b/jpsxdec/src/jpsxdec/util/player/IFrameProcessor.java new file mode 100644 index 0000000..63f5c55 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/IFrameProcessor.java @@ -0,0 +1,49 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.Nonnull; + +/** + * A class provided by the user that will accept raw frames, process them + * and then copy the final frame data into the provided int array + * in xRGB format. Top 8 bits ignored, next 24 bits are red, green, and blue. + */ +public interface IFrameProcessor { + void processFrame(@Nonnull T frame, @Nonnull int[] drawHere); +} diff --git a/jpsxdec/src/jpsxdec/util/player/IMediaDataReader.java b/jpsxdec/src/jpsxdec/util/player/IMediaDataReader.java new file mode 100644 index 0000000..304ef55 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/IMediaDataReader.java @@ -0,0 +1,51 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.Nonnull; + +/** + * A class provided by the user that will feed audio and video data into the + * supplied {@link PlayController}. Note that this is the same + * {@link PlayController} that is used to set this reader + * {@link PlayController#setReader(jpsxdec.util.player.IMediaDataReader)}, + * so the {@link PlayController} can be accessed either way. + */ +public interface IMediaDataReader { + void demuxThread(@Nonnull PlayController controller) throws StopPlayingException; +} diff --git a/jpsxdec/src/jpsxdec/util/player/IDecodableFrame.java b/jpsxdec/src/jpsxdec/util/player/IPreprocessedFrameWriter.java similarity index 86% rename from jpsxdec/src/jpsxdec/util/player/IDecodableFrame.java rename to jpsxdec/src/jpsxdec/util/player/IPreprocessedFrameWriter.java index 4f5c80c..9126d8d 100644 --- a/jpsxdec/src/jpsxdec/util/player/IDecodableFrame.java +++ b/jpsxdec/src/jpsxdec/util/player/IPreprocessedFrameWriter.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -39,14 +39,7 @@ import javax.annotation.Nonnull; -public interface IDecodableFrame { - void decodeVideo(@Nonnull int[] aiDrawHere); - - /** Returns the time the frame should be displayed, in milliseconds - * from the beginning of the movie. */ - long getPresentationTime(); - - /** Optional */ - void returnToPool(); +public interface IPreprocessedFrameWriter { + void writeFrame(@Nonnull Object frame, long lngPresentationNanos) throws StopPlayingException; } diff --git a/jpsxdec/src/jpsxdec/util/player/ObjectPlayStream.java b/jpsxdec/src/jpsxdec/util/player/ObjectPlayStream.java deleted file mode 100644 index 130a2ab..0000000 --- a/jpsxdec/src/jpsxdec/util/player/ObjectPlayStream.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 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.util.player; - -import java.util.Arrays; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; - -/** Very powerful, thread-safe, blocking queue with the ability to specify - * behavior when taking and adding items. */ -class ObjectPlayStream { - - private static final boolean DEBUG = false; - - private static enum WRITE { - OPEN, - CLOSED, - } - - private static enum READ { - OPEN, - PAUSED, - CLOSED, - } - - private final Object _eventSync = new Object(); - - private volatile WRITE _eWriteState = WRITE.OPEN; - private volatile READ _eReadState = READ.PAUSED; - - @Nonnull - protected final T[] _aoQueue; - protected int _iHeadPos; - protected int _iTailPos; - private int _iSize; - - public ObjectPlayStream(int iCapacity) { - _aoQueue = (T[]) new Object[iCapacity]; - _iSize = _iHeadPos = _iTailPos = 0; - } - - ////////////////////////////////// - - public @Nonnull Object getSyncObject() { - return _eventSync; - } - - void writerOpen() { - synchronized (_eventSync) { - _eWriteState = WRITE.OPEN; - } - } - - public void writerClose() { - synchronized (_eventSync) { - _eWriteState = WRITE.CLOSED; - _eventSync.notifyAll(); - } - } - - public void readerPause() { - synchronized (_eventSync) { - _eReadState = READ.PAUSED; - } - } - - public void readerOpen() { - synchronized (_eventSync) { - _eReadState = READ.OPEN; - _eventSync.notifyAll(); - } - } - - public void readerClose() { - synchronized (_eventSync) { - _eReadState = READ.CLOSED; - clear(); - _eventSync.notifyAll(); - } - } - - private void clear() { - Arrays.fill(_aoQueue, null); - _iSize = _iHeadPos = _iTailPos = 0; - } - - public boolean isReaderOpen() { - return _eReadState == READ.OPEN; - } - - public boolean isReaderOpenPaused() { - return _eReadState == READ.PAUSED; - } - - public boolean isReaderClosed() { - return _eReadState == READ.CLOSED; - } - - ///////////////////////////////// - - /** Returns true if object was added, or false if it wasn't. - * This method may block. The object must not be null. */ - public boolean write(@Nonnull T o) throws InterruptedException { - if (o == null) - throw new IllegalArgumentException(); - - if (DEBUG) System.out.println(Thread.currentThread().getName() + " add("+o.toString()+")"); - - synchronized (_eventSync) { - while (true) { - if (_eWriteState == WRITE.CLOSED || _eReadState == READ.CLOSED) { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " closed: returning false"); - return false; - } else if (isFull()) { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " full: waiting"); - _eventSync.wait(); - } else { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " writing " + o.toString()); - enqueue(o); - _iSize++; - if (DEBUG) System.out.println(Thread.currentThread().getName() + " notifying other threads and returning"); - _eventSync.notifyAll(); - return true; - } - } - } - } - - private boolean isFull() { - return _iSize >= _aoQueue.length; - } - - protected void enqueue(@Nonnull T o) { - _aoQueue[_iTailPos] = o; - _iTailPos++; - if (_iTailPos >= _aoQueue.length) { - _iTailPos = 0; - } - } - - /** Retrieves the head of the queue. May return null if no object is removed. - * This method may block. */ - public @CheckForNull T read() throws InterruptedException { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " enter take()"); - - synchronized (_eventSync) { - while (true) { - if (_eReadState == READ.PAUSED) { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " paused: waiting"); - _eventSync.wait(); - } else if (_eReadState == READ.CLOSED) { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " reader closed: returning null"); - return null; - } else if (isEmpty()) { - if (_eWriteState == WRITE.CLOSED) { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " empty & writer closed: closing reader & returning null"); - _eReadState = READ.CLOSED; - return null; - } else { - if (DEBUG) System.out.println(Thread.currentThread().getName() + " empty: waiting"); - _eventSync.wait(); - } - } else { - return dequeue(); - } - } - } - } - - private boolean isEmpty() { - return _iSize <= 0; - } - - /** Always called within syncronized (_eventSync) */ - private @Nonnull T dequeue() { - T o = _aoQueue[_iHeadPos]; - if (DEBUG) System.out.println(Thread.currentThread().getName() + " removing object: " + o.toString()); - _aoQueue[_iHeadPos] = null; - _iHeadPos++; - if (_iHeadPos >= _aoQueue.length) - _iHeadPos = 0; - _iSize--; - _eventSync.notifyAll(); - return o; - } - -} diff --git a/jpsxdec/src/jpsxdec/util/player/PlayController.java b/jpsxdec/src/jpsxdec/util/player/PlayController.java index e38911e..503da60 100644 --- a/jpsxdec/src/jpsxdec/util/player/PlayController.java +++ b/jpsxdec/src/jpsxdec/util/player/PlayController.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -38,193 +38,172 @@ package jpsxdec.util.player; import java.awt.Canvas; -import java.util.WeakHashMap; +import java.io.OutputStream; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.LineUnavailableException; import jpsxdec.util.Fraction; -/** Interface to controlling a player. */ +/** Primary public interface to controlling a player. */ public class PlayController { public static final Fraction PAL_ASPECT_RATIO = new Fraction(59, 54); public static final Fraction NTSC_ASPECT_RATIO = new Fraction(10, 11); public static final Fraction SQUARE_ASPECT_RATIO = new Fraction(1, 1); - private static final boolean DEBUG = false; - - @Nonnull - private AudioVideoReader _demuxReader; - @CheckForNull private AudioPlayer _audPlayer; - - @CheckForNull - private VideoProcessor _vidProcessor; @CheckForNull private VideoPlayer _vidPlayer; @Nonnull - private IVideoTimer _vidTimer; - + private final VideoTimer _videoTimer; - public PlayController(@Nonnull AudioVideoReader reader) { - AudioFormat format = reader.getAudioFormat(); - if (format == null && !reader.hasVideo()) - throw new IllegalArgumentException("No audio or video?"); + @CheckForNull + private final VideoProcessor _videoProcessorThread; + @Nonnull + private final ReaderThread _readerThread; + - if (format != null) { - _audPlayer = new AudioPlayer(format, this); - _vidTimer = _audPlayer; - } - if (reader.hasVideo()) { - if (_vidTimer == null) { - _vidTimer = _vidPlayer = new VideoPlayer(null, this, reader.getVideoWidth(), reader.getVideoHeight()); - } else { - _vidPlayer = new VideoPlayer(_vidTimer, this, reader.getVideoWidth(), reader.getVideoHeight()); - } - - _vidProcessor = new VideoProcessor(_vidTimer, _vidPlayer); - } + public PlayController(@Nonnull AudioFormat audioFormat) { + _audPlayer = new AudioPlayer(audioFormat); + _videoTimer = _audPlayer; + _vidPlayer = null; + _videoProcessorThread = null; + _readerThread = new ReaderThread(_audPlayer, null, this); + } - _demuxReader = reader; - _demuxReader.init(_audPlayer, _vidProcessor); + public PlayController(int iWidth, int iHeight) { + this(iWidth, iHeight, null); + } + public PlayController(int iWidth, int iHeight, @CheckForNull AudioFormat audioFormat) { + if (audioFormat != null) { + _audPlayer = new AudioPlayer(audioFormat); + _videoTimer = _audPlayer; + } else { + _audPlayer = null; + _videoTimer = new VideoClock(); + } + _vidPlayer = new VideoPlayer(_videoTimer, iWidth, iHeight); + _videoProcessorThread = new VideoProcessor(_videoTimer, _vidPlayer); + _readerThread = new ReaderThread(_audPlayer, _videoProcessorThread, this); } - public void start() throws LineUnavailableException { - _demuxReader.startBuffering(); - if (_audPlayer != null) - _audPlayer.startPaused(); - if (_vidProcessor != null) - _vidProcessor.startBuffering(); - if (_vidPlayer != null) - _vidPlayer.startPaused(); - java.awt.EventQueue.invokeLater(new NotifyLater(Event.Start)); + public void setReader(@Nonnull IMediaDataReader reader) { + _readerThread.setReader(reader); } - public void stop() { - synchronized (_vidTimer.getSyncObject()) { - // stop feeding data for starters - _demuxReader.stop(); + public void setVidProcressor(@Nonnull IFrameProcessor processor) { + if (_videoProcessorThread == null) + throw new IllegalArgumentException(); + _videoProcessorThread.setProcessor(processor); + } - if (_audPlayer != null) { - _audPlayer.stop(); - } + public void activate() throws PlayerException { + try { + _videoTimer.initPaused(); + if (_videoProcessorThread != null) + _videoProcessorThread.start(); if (_vidPlayer != null) - // must stop the player first so processor won't block - _vidPlayer.stop(); - if (_vidProcessor != null) - _vidProcessor.stop(); + _vidPlayer.startup(); + _readerThread.start(); + } catch (RuntimeException ex) { + terminate(); } } - public void pause() { - synchronized (_vidTimer.getSyncObject()) { - if (_vidPlayer != null) { - _vidPlayer.pause(); - } - if (_audPlayer != null) { - _audPlayer.pause(); - } else { - // TODO: stop vidTimer? - } - java.awt.EventQueue.invokeLater(new NotifyLater(Event.Pause)); - } + public void terminate() { + // this will kill the audio player if it is + _videoTimer.terminate(); + + // can't kill the reader thread, we're dependent on the reader to exit + // or trigger a StopPlayingException + + if (_vidPlayer != null) + _vidPlayer.terminate(); + if (_videoProcessorThread != null) + _videoProcessorThread.terminate(); } + public void pause() { + _videoTimer.pause(); + } public void unpause() { - synchronized (_vidTimer.getSyncObject()) { - if (_vidPlayer != null) { - _vidPlayer.unpause(); - } - if (_audPlayer != null) { - _audPlayer.unpause(); - } else { - // TODO: start vidTimer? - } - java.awt.EventQueue.invokeLater(new NotifyLater(Event.Unpause)); - } + _videoTimer.go(); + } + + public boolean isClosed() { + // (hopefully) single instruction (here) no don't need synchronized + return _videoTimer.isTerminated(); + } + + public boolean isPaused() { + // (hopefully) single instruction (here) no don't need synchronized + return _videoTimer.isPaused(); } + public boolean hasAudio() { + return (_audPlayer != null); + } + public boolean hasVideo() { + return (_vidPlayer != null); + } + + /** Only available if playing video. */ public @CheckForNull Canvas getVideoScreen() { if (_vidPlayer != null) - return _vidPlayer.getVideoCanvas(); + return _vidPlayer.getScreen(); else return null; } - public boolean hasVideo() { - return (_vidPlayer != null); + /** Only available if playing audio. */ + public @CheckForNull OutputStream getAudioOutputStream() { + if (_audPlayer != null) + return _audPlayer.getOutputStream(); + else + return null; } - public boolean hasAudio() { - return (_audPlayer != null); + /** Write completed frames to this writer. */ + public @CheckForNull IPreprocessedFrameWriter getFrameWriter() { + return _videoProcessorThread; } /** Adjust the rendered frame with this aspect ratio. */ public void setAspectRatio(@Nonnull Fraction aspectRatio) { if (_vidPlayer != null) - _vidPlayer.setAspectRatio(aspectRatio); + _vidPlayer.getScreen().setAspectRatio(aspectRatio); } /** Squash oversized frames to fit in TV. */ public void setSquashWidth(boolean blnSquash) { if (_vidPlayer != null) - _vidPlayer.setSquashWidth(blnSquash); + _vidPlayer.getScreen().setSquashWidth(blnSquash); } - void notifyDonePlaying() { - synchronized (_vidTimer.getSyncObject()) { - if ((_vidPlayer == null || _vidPlayer.isDone()) && - (_audPlayer == null || _audPlayer.isDone())) - { - java.awt.EventQueue.invokeLater(new NotifyLater(Event.Stop)); - } - } - } // ------------------------------------------------------------------------- // Listeners - @CheckForNull - private WeakHashMap _listeners; - - public void addLineListener(@Nonnull PlayerListener listener) { - if (_listeners == null) - _listeners = new WeakHashMap(); - _listeners.put(listener, Boolean.TRUE); + public void addEventListener(@Nonnull PlayerListener listener) { + _videoTimer.addEventListener(listener); } - public void removeLineListener(@Nonnull PlayerListener listener) { - if (_listeners != null) - _listeners.remove(listener); + public void removeEventListener(@Nonnull PlayerListener listener) { + _videoTimer.removeEventListener(listener); } public static enum Event { - Start, - Stop, + Play, + End, Pause, - Unpause - } - public static interface PlayerListener { - void update(@Nonnull Event eEvent); } - private class NotifyLater implements Runnable { - private final Event _eEvent; - public NotifyLater(@Nonnull Event eEvent) { - _eEvent = eEvent; - } - public void run() { - if (_listeners == null) - return; - for (PlayerListener listener : _listeners.keySet()) { - listener.update(_eEvent); - } - } + public static interface PlayerListener { + void event(@Nonnull Event eEvent); } } diff --git a/jpsxdec/src/jpsxdec/util/IOException6.java b/jpsxdec/src/jpsxdec/util/player/PlayerException.java similarity index 79% rename from jpsxdec/src/jpsxdec/util/IOException6.java rename to jpsxdec/src/jpsxdec/util/player/PlayerException.java index 0e03a87..5546171 100644 --- a/jpsxdec/src/jpsxdec/util/IOException6.java +++ b/jpsxdec/src/jpsxdec/util/player/PlayerException.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -35,29 +35,24 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package jpsxdec.util; +package jpsxdec.util.player; -import java.io.IOException; +/** General exception to indicate that there is an issue with the player. */ +public class PlayerException extends Exception { -/** {@link IOException} in Java 5 doesn't have the - * {@link java.lang.Throwable} constructor. */ -public class IOException6 extends IOException { - - public IOException6(Throwable cause) { - super(); - initCause(cause); + public PlayerException() { } - public IOException6(String message, Throwable cause) { + public PlayerException(String message) { super(message); - initCause(cause); } - public IOException6(String message) { - super(message); + public PlayerException(String message, Throwable cause) { + super(message, cause); } - public IOException6() { + public PlayerException(Throwable cause) { + super(cause); } - + } diff --git a/jpsxdec/src/jpsxdec/util/player/ReaderThread.java b/jpsxdec/src/jpsxdec/util/player/ReaderThread.java new file mode 100644 index 0000000..2262799 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/ReaderThread.java @@ -0,0 +1,98 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** Handles the running the reader supplied by the user. */ +class ReaderThread implements Runnable { + + @CheckForNull + private final AudioPlayer _audioPlayer; + @CheckForNull + private final VideoProcessor _videoProcessor; + @Nonnull + private final PlayController _controller; + @Nonnull + private final Thread _thread; + + private IMediaDataReader _reader; + + public ReaderThread(@CheckForNull AudioPlayer audioPlayer, + @CheckForNull VideoProcessor videoProcessor, + @Nonnull PlayController controller) + { + _audioPlayer = audioPlayer; + _videoProcessor = videoProcessor; + _controller = controller; + _thread = new Thread(this, getClass().getName()); + } + + public void setReader(@Nonnull IMediaDataReader reader) { + _reader = reader; + } + + public void start() { + if (_reader == null) + throw new IllegalStateException(); + _thread.start(); + } + + public void run() { + try { + _reader.demuxThread(_controller); + } catch (StopPlayingException ex) { + // ok + // immediately terminate + _controller.terminate(); + return; + } catch (Throwable ex) { + // not ok + ex.printStackTrace(); + // immediately terminate + _controller.terminate(); + return; + } + if (_audioPlayer != null) + _audioPlayer.finish(); + if (_videoProcessor != null) + _videoProcessor.finish(); + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/IVideoTimer.java b/jpsxdec/src/jpsxdec/util/player/StopPlayingException.java similarity index 84% rename from jpsxdec/src/jpsxdec/util/player/IVideoTimer.java rename to jpsxdec/src/jpsxdec/util/player/StopPlayingException.java index f262caa..91ee61e 100644 --- a/jpsxdec/src/jpsxdec/util/player/IVideoTimer.java +++ b/jpsxdec/src/jpsxdec/util/player/StopPlayingException.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -37,14 +37,17 @@ package jpsxdec.util.player; -import javax.annotation.Nonnull; +import java.io.IOException; -/** Syncs the video playback to whatever underlying timer is used. */ -interface IVideoTimer { +/** Exception to indicate that all player operations should terminate + * immediately. */ +public class StopPlayingException extends IOException { - boolean waitToPresent(@Nonnull VideoPlayer.VideoFrame frame); - boolean shouldBeProcessed(long lngPresentationTime); - @Nonnull Object getSyncObject(); - long getNanoPlayTime(); + public StopPlayingException() { + } + + public StopPlayingException(Throwable cause) { + super(cause); + } } diff --git a/jpsxdec/src/jpsxdec/util/player/ThreadSafeEventListeners.java b/jpsxdec/src/jpsxdec/util/player/ThreadSafeEventListeners.java new file mode 100644 index 0000000..026a8c8 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/ThreadSafeEventListeners.java @@ -0,0 +1,81 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.WeakHashMap; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** Takes care of handling even listeners in a thread-safe way. */ +class ThreadSafeEventListeners { + + @CheckForNull + protected WeakHashMap _listeners; + + public synchronized void addEventListener(@Nonnull PlayController.PlayerListener listener) { + // need to syncronize this null check to avoid creating multiple listeners + if (_listeners == null) + _listeners = new WeakHashMap(); + // need to syncronize adding to the map because it is not thread safe + _listeners.put(listener, Boolean.TRUE); + } + public synchronized void removeEventListener(@Nonnull PlayController.PlayerListener listener) { + // need to syncronize this null check to avoid creating multiple listeners + if (_listeners != null) + // need to syncronize removing from the map because it is not thread safe + _listeners.remove(listener); + } + + public void fire(@Nonnull PlayController.Event event) { + // safe to check for null since it is never set to null again + if (_listeners != null) { + Collection listeners; + synchronized (this) { + // thread-safely make a copy of the listeners + listeners = new ArrayList(_listeners.keySet()); + } + // then iterate + for (PlayController.PlayerListener listener : listeners) { + listener.event(event); + } + } + + } +} diff --git a/jpsxdec/src/jpsxdec/util/player/VideoClock.java b/jpsxdec/src/jpsxdec/util/player/VideoClock.java new file mode 100644 index 0000000..8760c2c --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/VideoClock.java @@ -0,0 +1,133 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.Nonnull; + +/** A video timer backed by the system clock. + * Manages its own even thread and queue. */ +class VideoClock extends VideoTimer implements Runnable { + + private long _lngStartTime = -1; + private long _lngPausedTime = -1; + @Nonnull + private final Thread _eventThread; + // TODO want unbounded queue + private final ClosableArrayBlockingQueue _eventQueue = + new ClosableArrayBlockingQueue(100); + + public VideoClock() { + _eventThread = new Thread(this, getClass().getName() + " event dispatcher"); + } + + @Override + public synchronized void initPaused() { + _eventThread.start(); + } + + @Override + public synchronized void go() { + if (_lngStartTime < 0) { + // initial start + _lngStartTime = System.nanoTime(); + } else if (_lngPausedTime >= 0) { + // unpause + // removed the amount of time paused from the start time + _lngStartTime += System.nanoTime() - _lngPausedTime; + _lngPausedTime = -1; + } else { + // already playing + return; + } + // let the super change the state and notify waiters + super.go(); + _eventQueue.addWithCapacityCheck(PlayController.Event.Play); + } + + + @Override + public synchronized void pause() { + _lngPausedTime = System.nanoTime(); + super.pause(); + _eventQueue.addWithCapacityCheck(PlayController.Event.Play); + } + + public synchronized long getNanoTime() { + if (_lngStartTime < 0) + return 0; + else if (_lngPausedTime >= 0) + return _lngPausedTime - _lngStartTime; + else + return System.nanoTime() - _lngStartTime; + } + + // TODO: pause the playback if we are waiting for a frame to arrive? + + @Override + public synchronized void videoDone() { + // when the video is done, this timer is done + super.terminate(); + try { + _eventQueue.add(PlayController.Event.End); + _eventQueue.closeWhenEmpty(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Override + public synchronized void terminate() { + _eventQueue.closeNow(); + super.terminate(); + } + + public void run() { + try { + while (true) { + PlayController.Event event = _eventQueue.take(); + if (event == null) + break; + fire(event); + } + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } +} + + diff --git a/jpsxdec/src/jpsxdec/util/player/VideoPlayer.java b/jpsxdec/src/jpsxdec/util/player/VideoPlayer.java index dbbf5e5..f2e0cac 100644 --- a/jpsxdec/src/jpsxdec/util/player/VideoPlayer.java +++ b/jpsxdec/src/jpsxdec/util/player/VideoPlayer.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -37,134 +37,88 @@ package jpsxdec.util.player; -import java.awt.Canvas; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsEnvironment; -import java.awt.RenderingHints; -import java.awt.Transparency; -import java.awt.image.BufferStrategy; -import java.awt.image.BufferedImage; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import jpsxdec.util.Fraction; /** Video player thread manages the actual display of video frames. */ -class VideoPlayer implements Runnable, IVideoTimer { +class VideoPlayer implements Runnable { private static final boolean DEBUG = false; private static final int CAPACITY = 50; - private final ObjectPlayStream _frameDisplayQueue = - new ObjectPlayStream(CAPACITY); + private final ClosableArrayBlockingQueue _frameDisplayQueue = + new ClosableArrayBlockingQueue(CAPACITY); private final int _iWidth, _iHeight; - /** Only used for video only streams. */ - @CheckForNull - private Thread _thread; - @Nonnull private final VideoScreen _screen; @Nonnull - private final IVideoTimer _vidTimer; + private final VideoTimer _vidTimer; + @Nonnull - private final PlayController _controller; + private final Thread _thread; - public VideoPlayer(@CheckForNull IVideoTimer vidTimer, @Nonnull PlayController controller, + public VideoPlayer(@Nonnull VideoTimer vidTimer, int iWidth, int iHeight) { - if (vidTimer == null) - _vidTimer = this; - else - _vidTimer = vidTimer; - _controller = controller; + _vidTimer = vidTimer; _iWidth = iWidth; _iHeight = iHeight; - _screen = new VideoScreen(); - _frameDisplayQueue.writerClose(); - _frameDisplayQueue.readerClose(); + _screen = new VideoScreen(_iWidth, _iHeight); + _thread = new Thread(this, getClass().getName()); + } + + public void startup() { + _thread.start(); } public void run() { try { - VideoFrame frame; - while ((frame = _frameDisplayQueue.read()) != null) { - boolean blnPresent = _vidTimer.waitToPresent(frame); - if (!blnPresent) { + DecodedVideoFrame frame; + while ((frame = _frameDisplayQueue.take()) != null) { + VideoTimer.ShowFrame showFrame = _vidTimer.waitToPresentFrame(frame.lngPresentationNanos); + if (showFrame == VideoTimer.ShowFrame.CLOSED) { + break; + } else if (showFrame == VideoTimer.ShowFrame.NO) { System.out.println("Timer says to discard frame"); } else { - if (DEBUG) System.out.println("===Displaying frame=== @" + frame.PresentationTime); + // show frame must be YES + if (DEBUG) System.out.println("===Displaying frame=== @" + frame.lngPresentationNanos); _screen.updateImage(frame); } } - if (DEBUG) System.out.println("Player received no frame for display, stopping"); - } catch (Throwable ex) { ex.printStackTrace(); + _vidTimer.terminate(); } finally { - _frameDisplayQueue.readerClose(); - _controller.notifyDonePlaying(); + System.out.println("VideoPlayer ending"); + + _frameDisplayQueue.closeNow(); + _vidTimer.videoDone(); } } - public boolean isDone() { - return _frameDisplayQueue.isReaderClosed(); + public void addFrame(@Nonnull DecodedVideoFrame frame) throws InterruptedException { + _frameDisplayQueue.add(frame); } - public void addFrame(VideoFrame frame) { - try { - _frameDisplayQueue.write(frame); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } + public @Nonnull VideoScreen getScreen() { + return _screen; } - public void startPaused() { - synchronized (_frameDisplayQueue.getSyncObject()) { - if (_frameDisplayQueue.isReaderClosed()) { - _frameDisplayQueue.writerOpen(); - _frameDisplayQueue.readerPause(); - _thread = new Thread(this, getClass().getName()); - _thread.start(); - _blnTimerStarting = true; - } - } - } - - public void stop() { - _frameDisplayQueue.readerClose(); + public void terminate() { + _frameDisplayQueue.closeNow(); + _vidTimer.terminate(); } public void pause() { - synchronized (_frameDisplayQueue.getSyncObject()) { - if (_frameDisplayQueue.isReaderOpen()) { - _lngTimerPausedTime = System.nanoTime(); - _frameDisplayQueue.readerPause(); - } - } + _vidTimer.pause(); } public void unpause() { - synchronized (_frameDisplayQueue.getSyncObject()) { - if (_frameDisplayQueue.isReaderOpenPaused()) { - _frameDisplayQueue.readerOpen(); - _lngTimerStartTime += System.nanoTime() - _lngTimerPausedTime; - } - } - } - - public void writerClose() { - _frameDisplayQueue.writerClose(); - } - - public @Nonnull Canvas getVideoCanvas() { - return _screen; + _vidTimer.go(); } public int getWidth() { @@ -174,244 +128,8 @@ public int getHeight() { return _iHeight; } - /** Adjust the rendered frame with this aspect ratio. */ - public void setAspectRatio(@Nonnull Fraction aspectRatio) { - _screen.setAspectRatio(aspectRatio); + public void finish() { + System.out.println("VideoPlayer request to end (if not already terminated)"); + _frameDisplayQueue.closeWhenEmpty(); } - - /** Squash oversized frames to fit in TV. */ - public void setSquashWidth(boolean blnSquash) { - _screen.setSquashWidth(blnSquash); - } - - // ---------------------------------------------------------------------- - - private class VideoScreen extends Canvas { - - /** Adjust the rendered frame with this aspect ratio. */ - @Nonnull - private Fraction __aspectRatio = PlayController.PAL_ASPECT_RATIO; - - @Nonnull - private Dimension __minDims; - - /** Squash oversized frames to fit in TV. */ - private boolean __blnSquashWidth = false; - @Nonnull - private Object __renderingHintInterpolation = - RenderingHints.VALUE_INTERPOLATION_BILINEAR; - //RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; - @CheckForNull - private BufferStrategy __buffStrategy; - @CheckForNull - private VideoFrame __currentFrame; - - public VideoScreen() { - setBackground(Color.BLACK); - updateDims(); - } - - public void setAspectRatio(@Nonnull Fraction aspectRatio) { - __aspectRatio = aspectRatio; - updateDims(); - } - - /** Squash oversized frames to fit in TV. */ - public void setSquashWidth(boolean blnSquash) { - __blnSquashWidth = blnSquash; - updateDims(); - } - - private void updateDims() { - __minDims = new Dimension(getSrcWidth(), - (int)(_iHeight * __aspectRatio.getNumerator() / __aspectRatio.getDenominator())); - } - - private void updateImage(@Nonnull VideoFrame frame) { - synchronized (getTreeLock()) { - if (__currentFrame != null) - __currentFrame.returnToPool(); - __currentFrame = frame; - if (__currentFrame == null) - return; - if (!isDisplayable()) { - // can't use or create BufferStrategy unless it is visible - System.out.println("Trying to play frame when canvas is hidden"); - return; - } - if (this.getWidth() == 0 || this.getHeight() == 0) { - return; - } - - if (__buffStrategy == null) { - createBufferStrategy(2); - System.out.println("BufferStrategy created"); - __buffStrategy = getBufferStrategy(); - } - Graphics g = __buffStrategy.getDrawGraphics(); - paint(g); - g.dispose(); - __buffStrategy.show(); - } - } - - private int getSrcWidth() { - if (__blnSquashWidth && _iWidth > 320) { - return 320; - } else { - return _iWidth; - } - } - - @Override - public void paint(@Nonnull Graphics g) { - final int iWinW = this.getWidth(); - final int iWinH = this.getHeight(); - g.setColor(Color.black); - g.fillRect(0, 0, iWinW, iWinH); - float fltConvertAspectRatio = (getSrcWidth() * __aspectRatio.getDenominator() ) / - (float)(_iHeight * __aspectRatio.getNumerator()); - float fltWinAspectRatio = iWinW / (float)iWinH; - int iDispW, iDispH; - if (fltConvertAspectRatio > fltWinAspectRatio) { - iDispW = iWinW; - iDispH = (int) (iDispW / fltConvertAspectRatio); - } else { - iDispH = iWinH; - iDispW = (int) (iDispH * fltConvertAspectRatio); - } - int iOfsX = (iWinW - iDispW) / 2; - int iOfsY = (iWinH - iDispH) / 2; - if (g instanceof Graphics2D) { - ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, __renderingHintInterpolation); - } - // if painting from Swing GUI, don't want Video thread to - // return frame to pool in the middle of us painting - synchronized (getTreeLock()) { - if (__currentFrame != null) - g.drawImage(__currentFrame.Img, iOfsX, iOfsY, iDispW, iDispH, null); - } - } - - @Override - public Dimension getMaximumSize() { - return null; - } - - @Override - public Dimension getMinimumSize() { - return __minDims; - } - - @Override - public Dimension getPreferredSize() { - return __minDims; - } - - } - - // ---------------------------------------------------------------------- - - public final VideoFramePool _videoFramePool = new VideoFramePool(); - public class VideoFramePool extends ObjectPool { - - @Override - protected VideoFrame createNewObject() { - return new VideoFrame(); - } - } - - - class VideoFrame { - @Nonnull - public final BufferedImage Img; - public long PresentationTime; - - public VideoFrame() { - GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); - Img = gc.createCompatibleImage(_iWidth, _iHeight, Transparency.OPAQUE); - } - - public void returnToPool() { - _videoFramePool.giveBack(this); - } - } - - // ---------------------------------------------------------------------- - - private static final int FRAME_DELAY_FUDGE_TIME = 50; - - private long _lngTimerStartTime; - private long _lngTimerPausedTime; - private boolean _blnTimerStarting; - - public long getNanoPlayTime() { - synchronized (_frameDisplayQueue.getSyncObject()) { - if (_frameDisplayQueue.isReaderOpen()) { - if (_blnTimerStarting) - return 0; - else - return System.nanoTime() - _lngTimerStartTime; - } else if (_frameDisplayQueue.isReaderOpenPaused()) { - return _lngTimerPausedTime - _lngTimerStartTime; - } else /* stopped */ { - return -1; - } - } - } - - public @Nonnull Object getSyncObject() { - return _frameDisplayQueue.getSyncObject(); - } - - public boolean shouldBeProcessed(long lngPresentationTime) { - synchronized (_frameDisplayQueue.getSyncObject()) { - if (_frameDisplayQueue.isReaderOpenPaused()) { - return true; - } else if (_frameDisplayQueue.isReaderOpen()) { - long lngPos = getNanoPlayTime(); - if (DEBUG) System.out.println("Play time = " + lngPos + " vs. Pres time = " + lngPresentationTime); - return (lngPresentationTime > lngPos); - } else /* stopped */ { - return false; - } - } - } - - - public boolean waitToPresent(@Nonnull VideoPlayer.VideoFrame frame) { - try { - synchronized (_frameDisplayQueue.getSyncObject()) { - while (true) { - if (_frameDisplayQueue.isReaderOpenPaused()) { - _frameDisplayQueue.getSyncObject().wait(); - // now loop again to see the new state - } else if (_frameDisplayQueue.isReaderOpen()) { - if (_blnTimerStarting) { - if (DEBUG) System.out.println("Timer is started, player is now playing"); - _blnTimerStarting = false; - _lngTimerStartTime = System.nanoTime(); - return true; - } else { - long lngPos = getNanoPlayTime(); - long lngSleepTime; - if ((lngSleepTime = frame.PresentationTime - lngPos) > FRAME_DELAY_FUDGE_TIME) { - lngSleepTime -= FRAME_DELAY_FUDGE_TIME; - _frameDisplayQueue.getSyncObject().wait(lngSleepTime / 1000000, (int)(lngSleepTime % 1000000)); - // now loop again to see if the state changed while waiting - } else { - return true; - } - } - } else /* stopped */ { - return false; - } - } - } - } catch (Throwable ex) { - ex.printStackTrace(); - return false; - } - } - } diff --git a/jpsxdec/src/jpsxdec/util/player/VideoProcessor.java b/jpsxdec/src/jpsxdec/util/player/VideoProcessor.java index 281e6f7..bbdc465 100644 --- a/jpsxdec/src/jpsxdec/util/player/VideoProcessor.java +++ b/jpsxdec/src/jpsxdec/util/player/VideoProcessor.java @@ -1,6 +1,6 @@ /* * jPSXdec: PlayStation 1 Media Decoder/Converter in Java - * Copyright (C) 2007-2019 Michael Sabin + * Copyright (C) 2019 Michael Sabin * All rights reserved. * * Redistribution and use of the jPSXdec code or any derivative works are @@ -37,50 +37,62 @@ package jpsxdec.util.player; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; /** Video processor thread manages the conversion of video source data * to a presentation image. */ -class VideoProcessor implements Runnable { +class VideoProcessor implements Runnable, IPreprocessedFrameWriter { private static final boolean DEBUG = false; private static final int CAPACITY = 50; - private final ObjectPlayStream _framesProcessingQueue = - new ObjectPlayStream(CAPACITY); - @CheckForNull - private Thread _thread; + private final ClosableArrayBlockingQueue _framesProcessingQueue = + new ClosableArrayBlockingQueue(CAPACITY); @Nonnull - private final IVideoTimer _vidTimer; + private final VideoTimer _vidTimer; @Nonnull private final VideoPlayer _vidPlayer; + @Nonnull + private final Thread _thread; + + private IFrameProcessor _processor; - public VideoProcessor(@Nonnull IVideoTimer timer, @Nonnull VideoPlayer player) { + VideoProcessor(@Nonnull VideoTimer timer, @Nonnull VideoPlayer player) { _vidTimer = timer; _vidPlayer = player; - _framesProcessingQueue.writerClose(); - _framesProcessingQueue.readerClose(); + _thread = new Thread(this, getClass().getName()); + } + + public void setProcessor(@Nonnull IFrameProcessor processor) { + _processor = processor; } + public void start() { + if (_processor == null) + throw new IllegalStateException(); + _thread.start(); + } + + @SuppressWarnings("unchecked") public void run() { - IDecodableFrame decodeFrame; + DecodableFrame decodeFrame; int[] aiImage = new int[_vidPlayer.getWidth() * _vidPlayer.getHeight()]; try { - while ((decodeFrame = _framesProcessingQueue.read()) != null) { + while ((decodeFrame = _framesProcessingQueue.take()) != null) { // check that we haven't passed presentation time - if (_vidTimer.shouldBeProcessed(decodeFrame.getPresentationTime())) + //System.out.println("Checking if to process frame at " + decodeFrame.lngPresentationNanos); + if (_vidTimer.shouldBeProcessed(decodeFrame.lngPresentationNanos)) { if (DEBUG) System.out.println("Processor processing frame :)"); - VideoPlayer.VideoFrame frame = _vidPlayer._videoFramePool.borrow(); - frame.PresentationTime = decodeFrame.getPresentationTime(); + DecodedVideoFrame frame = new DecodedVideoFrame(_vidPlayer.getWidth(), _vidPlayer.getHeight()); + frame.lngPresentationNanos = decodeFrame.lngPresentationNanos; // decode frame - decodeFrame.decodeVideo(aiImage); - frame.Img.setRGB(0, 0, - frame.Img.getWidth(), frame.Img.getHeight(), - aiImage, 0, frame.Img.getWidth()); + _processor.processFrame(decodeFrame.frame, aiImage); + frame.image.setRGB(0, 0, + frame.image.getWidth(), frame.image.getHeight(), + aiImage, 0, frame.image.getWidth()); // submit to vid player // will block if player is full _vidPlayer.addFrame(frame); @@ -90,38 +102,32 @@ public void run() { } } catch (Throwable ex) { ex.printStackTrace(); - } finally { - _framesProcessingQueue.readerClose(); - _vidPlayer.writerClose(); + terminate(); + return; } + System.out.println("VideoProcessor ending"); + _vidPlayer.finish(); } - public void writeFrame(@Nonnull IDecodableFrame frame) { + @SuppressWarnings("unchecked") + public void writeFrame(@Nonnull Object frame, long lngPresentationNanos) throws StopPlayingException { + if (DEBUG) System.out.println("Frame submitted for processing, present at " + lngPresentationNanos); try { - _framesProcessingQueue.write(frame); + if (!_framesProcessingQueue.add(new DecodableFrame(frame, lngPresentationNanos))) + throw new StopPlayingException(); } catch (InterruptedException ex) { - ex.printStackTrace(); + throw new StopPlayingException(ex); } } - - public void startBuffering() { - synchronized (_framesProcessingQueue.getSyncObject()) { - if (_framesProcessingQueue.isReaderClosed()) { - _framesProcessingQueue.writerOpen(); - _framesProcessingQueue.readerOpen(); - _thread = new Thread(this, getClass().getName()); - _thread.start(); - } - } - } - - /** Make sure player is stopped before calling this method or it will deadlock. */ - public void stop() { - _framesProcessingQueue.readerClose(); - } - public void writerClose() { - _framesProcessingQueue.writerClose(); + public void finish() { + System.out.println("VideoProcessor request to end"); + _framesProcessingQueue.closeWhenEmpty(); } + public void terminate() { + _framesProcessingQueue.closeNow(); + _vidPlayer.terminate(); + } + } diff --git a/jpsxdec/src/jpsxdec/util/player/VideoScreen.java b/jpsxdec/src/jpsxdec/util/player/VideoScreen.java new file mode 100644 index 0000000..f1965ae --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/VideoScreen.java @@ -0,0 +1,184 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferStrategy; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jpsxdec.util.Fraction; + +/** Canvas where video frames will appear on the screen. */ +class VideoScreen extends Canvas { + + private final int _iWidth, _iHeight; + + /** Adjust the rendered frame with this aspect ratio. */ + @Nonnull + private Fraction _aspectRatio = PlayController.PAL_ASPECT_RATIO; + + @Nonnull + private Dimension _minDims; + + /** Squash oversized frames to fit in TV. */ + private boolean _blnSquashWidth = false; + @Nonnull + private Object _renderingHintInterpolation = + RenderingHints.VALUE_INTERPOLATION_BILINEAR; + //RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; + @CheckForNull + private transient BufferStrategy _buffStrategy; + @CheckForNull + private transient DecodedVideoFrame _currentFrame; + + public VideoScreen(int iWidth, int iHeight) { + _iWidth = iWidth; + _iHeight = iHeight; + setBackground(Color.BLACK); + updateDims(); + } + + public void setAspectRatio(@Nonnull Fraction aspectRatio) { + _aspectRatio = aspectRatio; + updateDims(); + } + + public void setRenderingHint(Object interpolation) { + _renderingHintInterpolation = interpolation; + } + + /** Squash oversized frames to fit in TV. */ + public void setSquashWidth(boolean blnSquash) { + _blnSquashWidth = blnSquash; + updateDims(); + } + + private void updateDims() { + _minDims = new Dimension(getSrcWidth(), + (int)(_iHeight * _aspectRatio.getNumerator() / _aspectRatio.getDenominator())); + } + + public void updateImage(@Nonnull DecodedVideoFrame frame) { + synchronized (getTreeLock()) { + _currentFrame = frame; + if (_currentFrame == null) + return; + if (!isDisplayable()) { + // can't use or create BufferStrategy unless it is visible + System.out.println("Trying to play frame when canvas is hidden"); + return; + } + if (this.getWidth() == 0 || this.getHeight() == 0) { + return; + } + + if (_buffStrategy == null) { + createBufferStrategy(2); + System.out.println("BufferStrategy created"); + _buffStrategy = getBufferStrategy(); + } + + Graphics g = _buffStrategy.getDrawGraphics(); + paint(g); + g.dispose(); + _buffStrategy.show(); + } + } + + private int getSrcWidth() { + if (_blnSquashWidth && _iWidth > 320) { + return 320; + } else { + return _iWidth; + } + } + + @Override + public void paint(@Nonnull Graphics g) { + final int iWinW = this.getWidth(); + final int iWinH = this.getHeight(); + + // Clear the screen with black + g.setColor(Color.black); + g.fillRect(0, 0, iWinW, iWinH); + + float fltConvertAspectRatio = (getSrcWidth() * _aspectRatio.getDenominator() ) / + (float)(_iHeight * _aspectRatio.getNumerator()); + float fltWinAspectRatio = iWinW / (float)iWinH; + + int iDispW, iDispH; + if (fltConvertAspectRatio > fltWinAspectRatio) { + iDispW = iWinW; + iDispH = (int) (iDispW / fltConvertAspectRatio); + } else { + iDispH = iWinH; + iDispW = (int) (iDispH * fltConvertAspectRatio); + } + + int iOfsX = (iWinW - iDispW) / 2; + int iOfsY = (iWinH - iDispH) / 2; + + if (g instanceof Graphics2D) { + ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, _renderingHintInterpolation); + } + + if (_currentFrame != null) + g.drawImage(_currentFrame.image, iOfsX, iOfsY, iDispW, iDispH, null); + } + + @Override + public Dimension getMaximumSize() { + return null; + } + + @Override + public Dimension getMinimumSize() { + return _minDims; + } + + @Override + public Dimension getPreferredSize() { + return _minDims; + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/VideoTimer.java b/jpsxdec/src/jpsxdec/util/player/VideoTimer.java new file mode 100644 index 0000000..03b8c61 --- /dev/null +++ b/jpsxdec/src/jpsxdec/util/player/VideoTimer.java @@ -0,0 +1,150 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import javax.annotation.Nonnull; +import jpsxdec.util.player.PlayController.PlayerListener; + +/** Timer for when to present video frames. */ +abstract class VideoTimer { + + /** This tries to consider that there is a delay between the decision + * to display a frame, and the frame actually being displayed. + * This is a total guess. */ + private static final int FRAME_DELAY_NANO_FUDGE_TIME = 50; + + private static final boolean DEBUG = false; + + public enum ShowFrame { + YES, NO, CLOSED + } + + private enum State { + RUNNING, + PAUSED, + TERMINATED + } + + @Nonnull + private State _state = State.PAUSED; + private final ThreadSafeEventListeners _listeners = new ThreadSafeEventListeners(); + + public synchronized void go() { + _state = State.RUNNING; + this.notifyAll(); + } + + public void pause() { + // (hopefully) single instruction so thread safty is not needed + _state = State.PAUSED; + } + + public boolean isPaused() { + // (hopefully) single instruction so thread safty is not needed + return _state == State.PAUSED; + } + + public synchronized void terminate() { + _state = State.TERMINATED; + this.notifyAll(); + } + + abstract void initPaused() throws PlayerException; + abstract public void videoDone(); + abstract protected long getNanoTime(); + + + /** Returns if a frame that will appear at the presentation time should be processed. + * This is to avoid processing frames if their presentation time has already passed. */ + final public synchronized boolean shouldBeProcessed(long lngPresentationNanos) { + if (_state == State.RUNNING || _state == State.PAUSED) { + long lngPlayTime = getNanoTime(); + if (DEBUG) System.out.println("Processing: Current nano time " + lngPlayTime + ", presentation nano time " + lngPresentationNanos); + return lngPresentationNanos >= lngPlayTime; + } else { + return false; + } + } + + // TODO also wait for the video frame blocking queue if we want to pause if we're blocked because we're waiting for frames to be read? + /** Returns if the frame should be displayed. */ + final public synchronized @Nonnull ShowFrame waitToPresentFrame(long lngPresentationNanos) throws InterruptedException { + while (true) { + if (_state == State.TERMINATED) { + return ShowFrame.CLOSED; + } else if (_state == State.PAUSED) { + // this MUST be interrupted by terminate or go + this.wait(); + // TODO is there a way to regularly check if this thread is blocked forever + // like I do in other places? + } else { + long lngPos = getNanoTime(); + if (DEBUG) System.out.println("Presenting: Current nano time " + lngPos + ", presentation nano time " + lngPresentationNanos); + long lngSleepNanos; + // careful with the math here so we don't have to worry about int overflow + if ((lngSleepNanos = lngPresentationNanos - lngPos) > FRAME_DELAY_NANO_FUDGE_TIME) { + lngSleepNanos -= FRAME_DELAY_NANO_FUDGE_TIME; + // this CAN be interrupted by terminate (or go) + this.wait(lngSleepNanos / 1000000, (int)(lngSleepNanos % 1000000)); + // loop once more to see if the state changed + } else { + return ShowFrame.YES; + } + } + } + } + + public boolean isTerminated() { + // (hopefully) single instruction so synchronized is not needed + return _state == State.TERMINATED; + } + + public void addEventListener(@Nonnull PlayerListener listener) { + // listener object manages its own thread safety + _listeners.addEventListener(listener); + } + public void removeEventListener(@Nonnull PlayerListener listener) { + // listener object manages its own thread safety + _listeners.removeEventListener(listener); + } + protected void fire(@Nonnull PlayController.Event event) { + // listener object manages its own thread safety + _listeners.fire(event); + } + +} diff --git a/jpsxdec/src/jpsxdec/util/player/package.html b/jpsxdec/src/jpsxdec/util/player/package.html deleted file mode 100644 index 3ba2d83..0000000 --- a/jpsxdec/src/jpsxdec/util/player/package.html +++ /dev/null @@ -1,33 +0,0 @@ - - -jPSXdec - - - Cross-platform video/audio player. - -

        - You don't need the JMF to play videos. - -

        - The player utilizes several threads perform different tasks -

          -
        1. Demuxer thread ({@link jpsxdec.util.player.AudioVideoReader}) -
        2. Video processor thread ({@link jpsxdec.util.player.VideoProcessor}) -
        3. Video player thread ({@link jpsxdec.util.player.VideoPlayer}) -
        4. Audio player thread ({@link jpsxdec.util.player.AudioPlayer} wraps the - {@link javax.sound.sampled.SourceDataLine}) -
        -

        - Audio processing is done in the demuxer thread. -

        -

        - Packets of data are passed between these threads via - {@link jpsxdec.util.player.ObjectPlayStream}s. - -

        - There is also the external Controller thread - ({@link jpsxdec.util.player.PlayController}) that is typically the gui. - - - - diff --git a/jpsxdec/test/AllTestsSuite.java b/jpsxdec/test/AllTestsSuite.java index 54b5fd3..90e32c7 100644 --- a/jpsxdec/test/AllTestsSuite.java +++ b/jpsxdec/test/AllTestsSuite.java @@ -47,6 +47,7 @@ jpsxdec.TestLog.class, jpsxdec.adpcm.SpuDecodeCorruption.class, jpsxdec.adpcm.XaDecodeCorruption.class, + jpsxdec.cmdline.Command_StaticTest.class, jpsxdec.discitems.DiscItemTest.class, jpsxdec.discitems.SerializedDiscItemTest.class, jpsxdec.indexing.DiscIndexerXaAudioTest.class, diff --git a/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.bs b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.bs new file mode 100644 index 0000000..9671ed0 Binary files /dev/null and b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.bs differ diff --git a/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.java b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.java new file mode 100644 index 0000000..fdf527a --- /dev/null +++ b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.java @@ -0,0 +1,179 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.cmdline; + +import argparser.StringHolder; +import java.io.File; +import java.io.FileOutputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import jpsxdec.i18n.FeedbackStream; +import jpsxdec.psxvideo.bitstreams.BitStreamUncompressor_STRv2; +import jpsxdec.psxvideo.mdec.MdecCode; +import jpsxdec.psxvideo.mdec.MdecInputStream; +import jpsxdec.psxvideo.mdec.MdecInputStreamReader; +import jpsxdec.util.ArgParser; +import jpsxdec.util.IO; +import static org.junit.Assert.*; +import org.junit.Test; + + +public class Command_StaticTest { + + + @Test + public void testValidate() { + Command_Static testSubject = new Command_Static(); + assertNull(testSubject.validate("bs")); + assertNull(testSubject.validate("mdec")); + assertNotNull(testSubject.validate("invalid")); + } + + @Test + public void testBadArgs() throws CommandLineException { + testBadArgs("-dim", "bad"); + testBadArgs("-dim", "1"); + testBadArgs("-dim", "x"); + testBadArgs("-dim", "1xa"); + testBadArgs("-dim", "0x0x0"); + testBadArgs("-dim", "16x16", "-fmt", "bad format"); + testBadArgs("-dim", "16x16", "-fmt", "avi:rgb"); + testBadArgs("-dim", "16x16", "-fmt", "mdec"); + testBadArgs("-dim", "16x16", "-fmt", "png", "-q", "high"); + testBadArgs("-dim", "16x16", "-fmt", "png", "-up", "bad upsample"); + } + + private void testBadArgs(String... asArgs) { + ArgParser ap = new ArgParser(asArgs); + Command_Static testSubject = new Command_Static(); + assertNull(testSubject.validate("mdec")); + testSubject.init(new ArgParser(new String[] {"-static"}), new StringHolder("ignored for this test"), null, new FeedbackStream()); + + try { + testSubject.execute(ap); + fail("Expected " + CommandLineException.class); + } catch (CommandLineException ex) { + // expected + } + } + + private static final String TEST_BS_FILE = Command_StaticTest.class.getSimpleName() + ".bs"; + private static final String TEST_MDEC_FILE = Command_StaticTest.class.getSimpleName() + ".mdec"; + + @Test + public void testBs2Mdec() throws Exception { + ArgParser ap = new ArgParser(new String[] {"-dim", "16x16", "-fmt", "mdec"}); + Command_Static testSubject = new Command_Static(); + assertNull(testSubject.validate("bs")); + + // put an input test file in a temp directory + File inFile = testutil.Util.resourceAsTempFile(Command_StaticTest.class, TEST_BS_FILE); + // run command + testSubject.init(new ArgParser(new String[] {"-static"}), new StringHolder(inFile.getPath()), null, new FeedbackStream()); + testSubject.execute(ap); + // I guess check the output file + assertTrue(new File(Command_StaticTest.class.getSimpleName()+"_16x16.mdec").exists()); + } + + @Test + public void testBs2Png() throws Exception { + ArgParser ap = new ArgParser(new String[] {"-dim", "16x16", "-fmt", "png", "-up", "nearestneighbor"}); + Command_Static testSubject = new Command_Static(); + assertNull(testSubject.validate("bs")); + + // put an input test file in a temp directory + File inFile = testutil.Util.resourceAsTempFile(Command_StaticTest.class, TEST_BS_FILE); + // run command + testSubject.init(new ArgParser(new String[] {"-static"}), new StringHolder(inFile.getPath()), null, new FeedbackStream()); + testSubject.execute(ap); + // I guess check the output file + assertTrue(new File(Command_StaticTest.class.getSimpleName()+".png").exists()); + } + + + @Test + public void testMdec2Jpg() throws Exception { + ArgParser ap = new ArgParser(new String[] {"-dim", "16x16", "-fmt", "jpg", "-up", "nearestneighbor"}); + Command_Static testSubject = new Command_Static(); + assertNull(testSubject.validate("mdec")); + + // put an input test file in a temp directory + File inFile = testutil.Util.resourceAsTempFile(Command_StaticTest.class, TEST_MDEC_FILE); + // run command + testSubject.init(new ArgParser(new String[] {"-static"}), new StringHolder(inFile.getPath()), null, new FeedbackStream()); + testSubject.execute(ap); + // I guess check the output file + assertTrue(new File(Command_StaticTest.class.getSimpleName()+".jpg").exists()); + } + + public static void main(String[] args) throws Exception { + System.out.println("======================================================"); + System.out.println("================ Generating test data ================"); + System.out.println("======================================================"); + // A blank image with a single macro block (16x16) + final List codes = Arrays.asList( + new MdecCode(1, 0), new MdecCode().setToEndOfData(), + new MdecCode(1, 0), new MdecCode().setToEndOfData(), + new MdecCode(1, 0), new MdecCode().setToEndOfData(), + new MdecCode(1, 0), new MdecCode().setToEndOfData(), + new MdecCode(1, 0), new MdecCode().setToEndOfData(), + new MdecCode(1, 0), new MdecCode().setToEndOfData()); + + MdecInputStream mis = new ArrayListMdecInputStream(codes); + FileOutputStream fos = new FileOutputStream(TEST_MDEC_FILE); + MdecInputStreamReader.writeMdecBlocks(mis, fos, 6); + fos.close(); + + mis = new ArrayListMdecInputStream(codes); + BitStreamUncompressor_STRv2.BitStreamCompressor_STRv2 x = new BitStreamUncompressor_STRv2.BitStreamCompressor_STRv2(1); + byte[] abData = x.compress(mis); + IO.writeFile(TEST_BS_FILE, abData); + } + + private static class ArrayListMdecInputStream implements MdecInputStream { + private final Iterator it; + public ArrayListMdecInputStream(List codes) { + it = codes.iterator(); + } + public boolean readMdecCode(MdecCode code) { + code.setFrom(it.next()); + return code.isEOD(); + } + } +} diff --git a/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.mdec b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.mdec new file mode 100644 index 0000000..5e979b4 Binary files /dev/null and b/jpsxdec/test/jpsxdec/cmdline/Command_StaticTest.mdec differ diff --git a/jpsxdec/test/jpsxdec/discitems/DiscItemTest.java b/jpsxdec/test/jpsxdec/discitems/DiscItemTest.java index 03639cd..741c6c0 100644 --- a/jpsxdec/test/jpsxdec/discitems/DiscItemTest.java +++ b/jpsxdec/test/jpsxdec/discitems/DiscItemTest.java @@ -43,6 +43,7 @@ import jpsxdec.i18n.FeedbackStream; import jpsxdec.i18n.ILocalizedMessage; import jpsxdec.i18n.UnlocalizedMessage; +import jpsxdec.i18n.log.ILocalizedLogger; import jpsxdec.i18n.log.ProgressLogger; import jpsxdec.util.ArgParser; import org.junit.*; @@ -84,7 +85,7 @@ private static class SB extends DiscItemSaverBuilder { public boolean copySettingsTo(DiscItemSaverBuilder other) { return false; } public void printHelp(FeedbackStream fbs) {} public void commandLineOptions(ArgParser ap, FeedbackStream fbs) {} - public void printSelectedOptions(FeedbackStream fbs) {} + public void printSelectedOptions(ILocalizedLogger log) {} public ILocalizedMessage getOutputSummary() { return new UnlocalizedMessage("test"); } public DiscItemSaverBuilderGui getOptionPane() { return new SBG(); } public void startSave(ProgressLogger pl, File directory) {} diff --git a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/BitReader.java b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/BitReader.java index c8d7669..83216c9 100644 --- a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/BitReader.java +++ b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/BitReader.java @@ -206,14 +206,15 @@ public void testPerformance() { } for (int i = 0; i < MAKERS.length; i++) { - System.out.println(MAKERS[i].getClass()+" "+alngDuration[i]); + System.out.println(MAKERS[i]+" "+alngDuration[i]); } - assertTrue(alngDuration[2] + " !< " + alngDuration[0] + " if this fails it's because there is a faster way to implement the bit reader", - alngDuration[2] < alngDuration[0]); - assertTrue(alngDuration[2] + " !< " + alngDuration[1] + " if this fails it's because there is a faster way to implement the bit reader", - alngDuration[2] < alngDuration[1]); - + assertTrue(alngDuration[1] + " !< " + alngDuration[0] + " if this fails it's because there is a faster way to implement the bit reader", + alngDuration[1] < alngDuration[0]); + assertTrue(alngDuration[1] + " !< " + alngDuration[2] + " if this fails it's because there is a faster way to implement the bit reader", + alngDuration[1] < alngDuration[2]); + assertTrue(alngDuration[1] + " !< " + alngDuration[3] + " if this fails it's because there is a faster way to implement the bit reader", + alngDuration[1] < alngDuration[3]); } private interface ReaderMaker { @@ -222,23 +223,34 @@ private interface ReaderMaker { private static ReaderMaker[] MAKERS = { new ReaderMaker() { public ArrayBitReader make(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { - return new LoopSkip(abData, iDataSize, blnLittleEndian, iReadStart); + return new PreCheckBitSkip(abData, iDataSize, blnLittleEndian, iReadStart); + } + public String toString() { return PreCheckBitSkip.class.getSimpleName(); } + }, + new ReaderMaker() { + // This is the one implemented in ArrayBitReader + public ArrayBitReader make(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { + return new PostCheckBitSkip(abData, iDataSize, blnLittleEndian, iReadStart); } + public String toString() { return PostCheckBitSkip.class.getSimpleName(); } }, new ReaderMaker() { public ArrayBitReader make(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { - return new BitSkip(abData, iDataSize, blnLittleEndian, iReadStart); + return new PreCheckModSkip(abData, iDataSize, blnLittleEndian, iReadStart); } + public String toString() { return PreCheckModSkip.class.getSimpleName(); } }, new ReaderMaker() { public ArrayBitReader make(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { - return new ModSkip(abData, iDataSize, blnLittleEndian, iReadStart); + return new PostCheckModSkip(abData, iDataSize, blnLittleEndian, iReadStart); } + public String toString() { return PostCheckModSkip.class.getSimpleName(); } }, }; - private static class LoopSkip extends ArrayBitReader { - public LoopSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { + private static class PreCheckBitSkip extends ArrayBitReader { + + public PreCheckBitSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { super(abData, iDataSize, blnLittleEndian, iReadStart); } @@ -246,8 +258,8 @@ public LoopSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iRead public void skipBits(int iCount) throws MdecException.EndOfStream { _iBitsLeft -= iCount; if (_iBitsLeft < 0) { - _iByteOffset += -(_iBitsLeft / 16)*2; - _iBitsLeft = _iBitsLeft % 16; + _iByteOffset += ((-_iBitsLeft) >> 4) << 1; + _iBitsLeft = -((-_iBitsLeft) & 0xf); if (_iBitsLeft < 0) { _iBitsLeft += 16; _iByteOffset += 2; @@ -263,15 +275,15 @@ public void skipBits(int iCount) throws MdecException.EndOfStream { } } - private static class BitSkip extends ArrayBitReader { + // This is the one implemented in ArrayBitReader + private static class PostCheckBitSkip extends ArrayBitReader { - public BitSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { + public PostCheckBitSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { super(abData, iDataSize, blnLittleEndian, iReadStart); } @Override public void skipBits(int iCount) throws MdecException.EndOfStream { - _iBitsLeft -= iCount; if (_iBitsLeft < 0) { _iByteOffset += ((-_iBitsLeft) >> 4) << 1; @@ -293,8 +305,34 @@ public void skipBits(int iCount) throws MdecException.EndOfStream { } } - private static class ModSkip extends ArrayBitReader { - public ModSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { + private static class PreCheckModSkip extends ArrayBitReader { + public PreCheckModSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { + super(abData, iDataSize, blnLittleEndian, iReadStart); + } + + @Override + public void skipBits(int iCount) throws MdecException.EndOfStream { + _iBitsLeft -= iCount; + if (_iBitsLeft < 0) { + _iByteOffset += -(_iBitsLeft / 16)*2; + _iBitsLeft = _iBitsLeft % 16; + if (_iBitsLeft < 0) { + _iBitsLeft += 16; + _iByteOffset += 2; + } + if (_iByteOffset > _iDataSize) { + _iBitsLeft = 0; + _iByteOffset = _iDataSize; + throw new MdecException.EndOfStream(_iByteOffset + " > " + _iDataSize); + } else if (_iBitsLeft > 0) { + _siCurrentWord = readWord(_iByteOffset-2); + } + } + } + } + + private static class PostCheckModSkip extends ArrayBitReader { + public PostCheckModSkip(byte[] abData, int iDataSize, boolean blnLittleEndian, int iReadStart) { super(abData, iDataSize, blnLittleEndian, iReadStart); } diff --git a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv2.java b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv2.java index a566f84..5bbddd6 100644 --- a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv2.java +++ b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv2.java @@ -38,8 +38,8 @@ package jpsxdec.psxvideo.bitstreams; import java.io.*; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecException; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.util.IO; import org.junit.*; import static org.junit.Assert.*; diff --git a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv3.java b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv3.java index 0c4d56e..d0ea696 100644 --- a/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv3.java +++ b/jpsxdec/test/jpsxdec/psxvideo/bitstreams/STRv3.java @@ -54,9 +54,9 @@ import jpsxdec.psxvideo.encode.MdecEncoder; import jpsxdec.psxvideo.encode.PsxYCbCrImage; import jpsxdec.psxvideo.mdec.Calc; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import jpsxdec.psxvideo.mdec.idct.IDCT_double; import jpsxdec.psxvideo.mdec.idct.StephensIDCT; import jpsxdec.util.BinaryDataNotRecognized; @@ -225,7 +225,7 @@ public void encodeDcMaxDiff() throws Exception { } - public static class MdecInputStreamIterator extends MdecInputStream { + public static class MdecInputStreamIterator implements MdecInputStream { private final Iterator _codes; @@ -235,7 +235,7 @@ public MdecInputStreamIterator(Iterator codes) { @Override public boolean readMdecCode(MdecCode code) { - code.set(_codes.next()); + code.setFrom(_codes.next()); return code.isEOD(); } } @@ -294,8 +294,7 @@ public static void testStreamFile(String sFile) throws Exception { for (String sCode : asCodes) { try { v3.readMdecCode(code); - if (!sCode.equals(code.toString())) - System.out.println("Line "+lnr.getLineNumber()+": "+sLine); + assertEquals("Line "+lnr.getLineNumber()+": "+sLine, sCode, formatMdec(code)); } catch (MdecException.ReadCorruption ex) { if (!sCode.startsWith("!")) { AssertionError e = new AssertionError("Line "+lnr.getLineNumber()+": "+sLine); @@ -308,7 +307,12 @@ public static void testStreamFile(String sFile) throws Exception { lnr.close(); } - + private static String formatMdec(MdecCode code) { + if (code.isEOD()) + return "EOD"; + else + return "("+code.getTop6Bits()+", "+code.getBottom10Bits()+")"; + } private static abstract class Str3DcGenerator { private BitStreamUncompressor_STRv3 _v3; @@ -333,7 +337,7 @@ private MdecCode nextCode() throws MdecException.EndOfStream, MdecException.Read _v3.readMdecCode(_code); if (_mdecCodesRead.length() > 0) _mdecCodesRead.append('+'); - _mdecCodesRead.append(_code); + _mdecCodesRead.append(formatMdec(_code)); if (_code.getBottom10Bits() % 4 != 0) throw new IllegalStateException(_code.toString()); return _code; @@ -492,6 +496,9 @@ protected void writeSetupBits() throws IOException { } public static void main(String[] args) throws Exception { + System.out.println("*************************************"); + System.out.println("** Generating DC test lookup files **"); + System.out.println("*************************************"); new DcGeneratorChroma().generate("v3_DC_CHROMA.txt"); new DcGeneratorLuma().generate("v3_DC_LUMA.txt"); } diff --git a/jpsxdec/test/jpsxdec/psxvideo/mdec/tojpeg/Mdec2JpegTest.java b/jpsxdec/test/jpsxdec/psxvideo/mdec/tojpeg/Mdec2JpegTest.java index 6d86490..8d22c65 100644 --- a/jpsxdec/test/jpsxdec/psxvideo/mdec/tojpeg/Mdec2JpegTest.java +++ b/jpsxdec/test/jpsxdec/psxvideo/mdec/tojpeg/Mdec2JpegTest.java @@ -38,9 +38,9 @@ package jpsxdec.psxvideo.mdec.tojpeg; import java.io.ByteArrayOutputStream; +import jpsxdec.psxvideo.mdec.MdecCode; import jpsxdec.psxvideo.mdec.MdecException; import jpsxdec.psxvideo.mdec.MdecInputStream; -import jpsxdec.psxvideo.mdec.MdecInputStream.MdecCode; import org.junit.*; import static org.junit.Assert.*; import testutil.Util; @@ -58,7 +58,7 @@ public static void setUpClass() throws Exception { public static void tearDownClass() throws Exception { } - private static class MStream extends MdecInputStream { + private static class MStream implements MdecInputStream { private final MdecCode[] _codes; private int _i = 0; @@ -69,7 +69,7 @@ public MStream(MdecCode[] _codes) { @Override public boolean readMdecCode(MdecCode code) { - code.set(_codes[_i++]); + code.setFrom(_codes[_i++]); return code.isEOD(); } } diff --git a/jpsxdec/test/jpsxdec/util/MiscTest.java b/jpsxdec/test/jpsxdec/util/MiscTest.java index 7956b6f..0fa1c7f 100644 --- a/jpsxdec/test/jpsxdec/util/MiscTest.java +++ b/jpsxdec/test/jpsxdec/util/MiscTest.java @@ -37,31 +37,13 @@ package jpsxdec.util; +import java.text.MessageFormat; import java.util.ArrayList; import org.junit.*; import static org.junit.Assert.*; public class MiscTest { - public MiscTest() { - } - - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - @Test public void testObjEq() { String x = "x"; @@ -73,4 +55,11 @@ public void testObjEq() { assertFalse(Misc.objectEquals(null, "b")); assertFalse(Misc.objectEquals("a", "b")); } + + @Test + public void testDateFromSeconds() { + assertEquals("0:00", MessageFormat.format("{0,time,m:ss}", Misc.dateFromSeconds(0))); + assertEquals("0:01", MessageFormat.format("{0,time,m:ss}", Misc.dateFromSeconds(1))); + assertEquals("1:30", MessageFormat.format("{0,time,m:ss}", Misc.dateFromSeconds(90))); + } } diff --git a/jpsxdec/test/jpsxdec/util/player/ClosableArrayBlockingQueueTest.java b/jpsxdec/test/jpsxdec/util/player/ClosableArrayBlockingQueueTest.java new file mode 100644 index 0000000..4361494 --- /dev/null +++ b/jpsxdec/test/jpsxdec/util/player/ClosableArrayBlockingQueueTest.java @@ -0,0 +1,254 @@ +/* + * jPSXdec: PlayStation 1 Media Decoder/Converter in Java + * Copyright (C) 2019 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.util.player; + +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +public class ClosableArrayBlockingQueueTest { + + @Rule + public Timeout globalTimeout = Timeout.seconds(10); // 10 seconds max per method tested + + private static final String OBJECT1 = "OBJECT1"; + + @Test + public void testAdd() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + assertTrue(queue.add(OBJECT1)); + assertFalse(queue.isClosed()); + } + + @Test + public void testAddTakeSameThread() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + assertTrue(queue.add(OBJECT1)); + String actual = queue.take(); + assertSame(OBJECT1, actual); + assertFalse(queue.isClosed()); + } + + @Test + public void testAddTake2SameThread() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + assertTrue(queue.add(OBJECT1)); + String actual = queue.take(); + assertSame(OBJECT1, actual); + try { + queue.take(); + fail("Expected " + IllegalStateException.class); + } catch (IllegalStateException ex) { + } + } + + @Test + public void testClosed() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + queue.closeNow(); + assertFalse(queue.add(OBJECT1)); + String actual = queue.take(); + assertNull(actual); + assertTrue(queue.isClosed()); + } + + @Test + public void testCloseNow() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(3); + + assertTrue(queue.add(OBJECT1)); + assertTrue(queue.add(OBJECT1)); + queue.closeNow(); + assertFalse(queue.add(OBJECT1)); + String actual = queue.take(); + assertNull(actual); + assertTrue(queue.isClosed()); + } + + @Test + public void testPoison() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(3); + + assertTrue(queue.add(OBJECT1)); + queue.closeWhenEmpty(); + assertFalse(queue.isClosed()); + assertFalse(queue.add(OBJECT1)); + assertFalse(queue.isClosed()); + String actual = queue.take(); + assertFalse(queue.isClosed()); + assertSame(OBJECT1, actual); + assertFalse(queue.isClosed()); + actual = queue.take(); + assertNull(actual); + assertTrue(queue.isClosed()); + } + + @Test + public void testTakeTakeAdd() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + OtherTake otherTake = new OtherTake(queue); + otherTake.start(); + try { + while (otherTake.isAlive() && otherTake.getState() != Thread.State.TIMED_WAITING) { + Thread.sleep(100); + } + + assertEquals(Thread.State.TIMED_WAITING, otherTake.getState()); + assertTrue(otherTake.isAlive()); + // check the thread stopped before setting these + assertNull(otherTake._ex); + assertNull(otherTake._taken); + + // add an object to the queue + assertTrue(queue.add(OBJECT1)); + // that will unblock the thread and it will exit + // wait for it to exit + otherTake.join(3000); + // fail if it didn't exit + assertFalse(otherTake.isAlive()); + + // make sure there was no exception + assertNull(otherTake._ex); + // and finally check that the object made it there + assertSame(OBJECT1, otherTake._taken); + + assertFalse(queue.isClosed()); + } finally { + otherTake.interrupt(); + } + } + + + @Test + public void testCloseThread() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + OtherTake otherTake = new OtherTake(queue); + otherTake.start(); + try { + while (otherTake.isAlive() && otherTake.getState() != Thread.State.TIMED_WAITING) { + Thread.sleep(100); + } + + assertEquals(Thread.State.TIMED_WAITING, otherTake.getState()); + assertTrue(otherTake.isAlive()); + // check the thread stopped before setting these + assertNull(otherTake._ex); + assertNull(otherTake._taken); + + // close both + queue.closeNow(); + // that will unblock the thread and it will exit + // wait for it to exit + otherTake.join(3000); + // fail if it didn't exit + assertFalse(otherTake.isAlive()); + + // make sure there was no exception + assertNull(otherTake._ex); + // and finally check that nothing was taken + assertNull(otherTake._taken); + + assertTrue(queue.isClosed()); + } finally { + otherTake.interrupt(); + } + } + + @Test + public void testPoisonThread() throws Exception { + ClosableArrayBlockingQueue queue = new ClosableArrayBlockingQueue(1); + + OtherTake otherTake = new OtherTake(queue); + otherTake.start(); + try { + while (otherTake.isAlive() && otherTake.getState() != Thread.State.TIMED_WAITING) { + Thread.sleep(100); + } + + assertEquals(Thread.State.TIMED_WAITING, otherTake.getState()); + assertTrue(otherTake.isAlive()); + // check the thread stopped before setting these + assertNull(otherTake._ex); + assertNull(otherTake._taken); + + // close + queue.closeWhenEmpty(); + // that will unblock the thread and it will exit + // wait for it to exit + otherTake.join(3000); + // fail if it didn't exit + assertFalse(otherTake.isAlive()); + + // make sure there was no exception + assertNull(otherTake._ex); + // and finally check that nothing was taken + assertNull(otherTake._taken); + + assertTrue(queue.isClosed()); + } finally { + otherTake.interrupt(); + } + } + + private static class OtherTake extends Thread { + private final ClosableArrayBlockingQueue _otherMine; + private transient String _taken; + private transient Exception _ex; + public OtherTake(ClosableArrayBlockingQueue otherMine) { + _otherMine = otherMine; + } + @Override + public void run() { + System.out.println("Thread started"); + try { + _taken = _otherMine.take(); + } catch (Exception ex) { + _ex = ex; + } + System.out.println("Thread ending"); + } + } + +} diff --git a/laintools/src/laintools/BINextrator.java b/laintools/src/laintools/BINextrator.java index 094f8ea..99829e7 100644 --- a/laintools/src/laintools/BINextrator.java +++ b/laintools/src/laintools/BINextrator.java @@ -49,7 +49,6 @@ public class BINextrator { public static int DebugVerbose = 2; public static void main(String[] args) { - //args = new String[] {"BIN.BIN-hacked", "bin-bin"}; if (args.length < 2) { System.out.println("Expecting 2 parameters: "); return; diff --git a/laintools/src/laintools/Lain_LAPKS.java b/laintools/src/laintools/Lain_LAPKS.java index 10e9ceb..0e9ec0b 100644 --- a/laintools/src/laintools/Lain_LAPKS.java +++ b/laintools/src/laintools/Lain_LAPKS.java @@ -54,6 +54,14 @@ public class Lain_LAPKS { private static boolean DEBUG = false; + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Expecting 2 parameters: LAPKS.BIN "); + return; + } + decodeLAPKS(args[0], args[1]); + } /** Decodes the numerous Lain poses from the LAPKS.BIN file. The LAPKS.BIN * file is the same on both disc 1 and disc 2. This function needs a @@ -90,15 +98,15 @@ public static int decodeLAPKS(String sInLAPKS_BIN, String sOutFileBase) { new PsxMdecIDCT_double(), cell.Width, cell.Height); BitStreamUncompressor_Lain uncompresor = BitStreamUncompressor_Lain.makeLain(cell.Data); oDecoder.decode(uncompresor); - RgbIntImage oRgb = new RgbIntImage(cell.Width, cell.Height); - oDecoder.readDecodedRgb(oRgb.getWidth(), oRgb.getHeight(), oRgb.getData()); + RgbIntImage rgb = new RgbIntImage(cell.Width, cell.Height); + oDecoder.readDecodedRgb(rgb.getWidth(), rgb.getHeight(), rgb.getData()); String s = String.format("%s%02d_f%02d", sOutFileBase, cell.PkIndex, cell.CellIndex); - BufferedImage bi = oRgb.toBufferedImage(); + BufferedImage bi = rgb.toBufferedImage(); int x, y, w, h; if (cell.Width == 320 && cell.Height == 240) { // don't do anything @@ -242,8 +250,8 @@ public static class LaPkCellIS { /* _Cell header_ * 2 bytes: Image Width * 2 bytes: Image Height - * 2 bytes: Chrominance Quantization Scale - * 2 bytes: Luminance Quantization Scale + * 2 bytes: Chroma Quantization Scale + * 2 bytes: Luma Quantization Scale * 4 bytes: Length of cell data in bytes (after this value) * 4 bytes: Number of run length codes? * (data length-4) bytes: width/16*height/16 compressed macro blocks