diff --git a/README.md b/README.md index ee007df..373d562 100644 --- a/README.md +++ b/README.md @@ -5,28 +5,30 @@ # vavi-sound +logo + Provides old school Japanese cell phone sounds library as `javax.sound` SPI
includes many ADPCM codecs and the [SSRC](https://github.com/shibatch/SSRC) sampling rate converter. ### Status -| **SPI** | **Codec** | **Description** | **IN Status** | **OUT Status** | **SPI Status** | **Comment** | -|:--------|:---------------------------------------------------------|:-------------------------------------|:-------------:|:--------------:|:---------------:|:-------------------------| -| midi | [MFi](src/main/java/vavi/sound/midi/mfi) | Japanese ring tone format | 🚧 | ✅ | ✅ | DoCoMo | -| midi | [SMAF](src/main/java/vavi/sound/midi/smaf) | YAMAHA ring tone format | 🚧 | ✅ | ✅ | au, Softbank | -| sampled | [MFi](src/main/java/vavi/sound/sampled/mfi) | Japanese ring tone format | ✅ | ✅ | ✅ | DoCoMo | -| sampled | [SMAF](src/main/java/vavi/sound/sampled/smaf) | YAMAHA ring tone format | ✅ | ✅ | ✅ | au, Softbank | -| sampled | [CCITT ADPCM](src/main/java/vavi/sound/adpcm/ccitt) | G711, G721, G723 | ✅ | ✅ | ✅ | | -| sampled | [DVI ADPCM](src/main/java/vavi/sound/adpcm/dvi) | DVI ADPCM | ✅ | ✅ | ✅ | | -| sampled | [IMA ADPCM](src/main/java/vavi/sound/adpcm/ima) | IMA ADPCM | ✅ | ✅ | ✅[1] | | -| sampled | [MA ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | YAMAHA ADPCM | ✅ | ✅ | ✅ | | -| sampled | [MS ADPCM](src/main/java/vavi/sound/adpcm/ms) | Microsoft ADPCM | ✅ | ✅ | ✅[1] | | -| sampled | [OKI ADPCM](src/main/java/vavi/sound/adpcm/oki) | OKI ADPCM | ✅ | ✅ | ✅ | | -| sampled | [ROHM ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | ROHM ADPCM | ✅ | ✅ | ✅ | | -| sampled | [VOX ADPCM](src/main/java/vavi/sound/adpcm/vox) | VOX ADPCM | ✅ | ✅ | ✅ | | -| sampled | [YAMAHA ADPCM](src/main/java/vavi/sound/adpcm/yamaha) | YAMAHA ADPCM | ✅ | ✅ | ✅ | | -| sampled | [YM2068 ADPCM](src/main/java/vavi/sound/adpcm/ym2608) | YAMAHA ADPCM | ✅ | ✅ | - | same as yamaha | -| sampled | [ssrc](src/main/java/vavi/sound/pcm/resampling/ssrc) | resampling | ✅ | - | ✅ | need to wait for phase 1 | +| **SPI** | **Codec** | **Description** | **IN Status** | **OUT Status** | **SPI Status** | **Comment** | +|:--------|:---------------------------------------------------------|:--------------------------|:-------------:|:--------------:|:----------------:|:-------------------------------| +| midi | [MFi](src/main/java/vavi/sound/midi/mfi) | Japanese ring tone format | 🚧 | ✅ | ✅ | DoCoMo | +| midi | [SMAF](src/main/java/vavi/sound/midi/smaf) | YAMAHA ring tone format | 🚧 | ✅ | ✅ | au, Softbank | +| sampled | [MFi](src/main/java/vavi/sound/sampled/mfi) | Japanese ring tone format | ✅ | ✅ | ✅ | DoCoMo | +| sampled | [SMAF](src/main/java/vavi/sound/sampled/smaf) | YAMAHA ring tone format | ✅ | ✅ | ✅ | au, Softbank | +| sampled | [CCITT ADPCM](src/main/java/vavi/sound/adpcm/ccitt) | G711, G721, G723 | ✅ | ✅ | ✅ | G721 cellphone w/ Fuetrek chip | +| sampled | [DVI ADPCM](src/main/java/vavi/sound/adpcm/dvi) | DVI ADPCM | ✅ | ✅ | ✅ | | +| sampled | [IMA ADPCM](src/main/java/vavi/sound/adpcm/ima) | IMA ADPCM | ✅ | ✅ | ✅ [1] | | +| sampled | [MA ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | YAMAHA ADPCM | ✅ | ✅ | ✅ | cellphone w/ YAMAHA MA chip | +| sampled | [MS ADPCM](src/main/java/vavi/sound/adpcm/ms) | Microsoft ADPCM | ✅ | ✅ | ✅ [1] | | +| sampled | [OKI ADPCM](src/main/java/vavi/sound/adpcm/oki) | OKI ADPCM | ✅ | ✅ | ✅ [1] | | +| sampled | [ROHM ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | ROHM ADPCM | ✅ | ✅ | ✅ | cellphone w/ Rohm chip | +| sampled | [VOX ADPCM](src/main/java/vavi/sound/adpcm/vox) | VOX ADPCM | ✅ | ✅ | ✅ [1] | | +| sampled | [YAMAHA ADPCM](src/main/java/vavi/sound/adpcm/yamaha) | YAMAHA ADPCM | ✅ | ✅ | ✅ [1] | | +| sampled | [YM2068 ADPCM](src/main/java/vavi/sound/adpcm/ym2608) | YAMAHA ADPCM | ✅ | ✅ | - | same as YAMAHA ADPCM | +| sampled | [ssrc](src/main/java/vavi/sound/pcm/resampling/ssrc) | resampling | ✅ | - | ✅ | need to wait for phase 1 | [1] wav file readable @@ -57,6 +59,10 @@ A. yes you can, follow those steps * github actions workflow on ubuntu java8 cannot deal line `PCM_SIGNED 8000.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian` +## References + + * https://github.com/shibatch/SSRC + ## TODO * use `Receiver` instead of `MetaEventListener` @@ -64,3 +70,6 @@ A. yes you can, follow those steps * on macos m2 ultra 1st pass is in a blink of an eye * ~~`ima`, `ms` adpcm: wav reader~~ * ~~`tritonus:tritonus-remaining:org.tritonus.sampled.file.WaveAudioFileReader`~~ + +--- +images by melody, cellphone \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3abd418..473bae1 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ vavi vavi-sound - 1.0.17 + 1.0.18 Vavi Sound API https://github.com/umjammer/vavi-sound @@ -202,7 +202,7 @@ TODO com.github.umjammer vavi-commons - 1.1.11 + 1.1.12 com.github.umjammer diff --git a/src/main/java/vavi/sound/LimitedInputStream.java b/src/main/java/vavi/sound/LimitedInputStream.java new file mode 100644 index 0000000..0112365 --- /dev/null +++ b/src/main/java/vavi/sound/LimitedInputStream.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.sound; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + + +/** + * MsWaveAudioFileReaderTest. + * + * @author Naohide Sano (nsano) + * @version 0.00 240330 nsano initial version
+ */ +public class LimitedInputStream extends FilterInputStream { + + private static final Logger logger = Logger.getLogger(LimitedInputStream.class.getName()); + + public static final String ERROR_MESSAGE_REACHED_TO_LIMIT = "stop reading, prevent form eof"; + + private final int limit; + + public LimitedInputStream(InputStream in) throws IOException { + this(in, in.available()); + } + + public LimitedInputStream(InputStream in, int limit) throws IOException { + super(in); + this.limit = limit; +logger.finer("limit: " + limit); + } + + private void check(int r) throws IOException { + if (in.available() < r) { +logger.fine("reached to limit"); + throw new IOException(ERROR_MESSAGE_REACHED_TO_LIMIT); + } + } + + @Override + public int read() throws IOException { + check(1); + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException { + check(b.length); + return super.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + check(len); + return super.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + check((int) n); + return super.skip(n); + } +} diff --git a/src/main/java/vavi/sound/mfi/vavi/AudioDataMessage.java b/src/main/java/vavi/sound/mfi/vavi/AudioDataMessage.java index 30370e9..0106f2e 100644 --- a/src/main/java/vavi/sound/mfi/vavi/AudioDataMessage.java +++ b/src/main/java/vavi/sound/mfi/vavi/AudioDataMessage.java @@ -171,7 +171,7 @@ public void readFrom(InputStream is) // type byte[] bytes = new byte[4]; - dis.read(bytes, 0, 4); + dis.readFully(bytes, 0, 4); String string = new String(bytes); if (!TYPE.equals(string)) { throw new InvalidMfiDataException("invalid audio data: " + string); diff --git a/src/main/java/vavi/sound/sampled/adpcm/AdpcmWaveAudioFileReader.java b/src/main/java/vavi/sound/sampled/adpcm/AdpcmWaveAudioFileReader.java index a9ef61d..068e1ca 100644 --- a/src/main/java/vavi/sound/sampled/adpcm/AdpcmWaveAudioFileReader.java +++ b/src/main/java/vavi/sound/sampled/adpcm/AdpcmWaveAudioFileReader.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.net.URL; import java.nio.file.Files; +import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import javax.sound.sampled.AudioFileFormat; @@ -22,6 +23,7 @@ import javax.sound.sampled.UnsupportedAudioFileException; import javax.sound.sampled.spi.AudioFileReader; +import vavi.sound.LimitedInputStream; import vavi.util.Debug; import vavi.util.win32.Chunk; import vavi.util.win32.WAVE; @@ -72,9 +74,6 @@ protected int getBufferSize() { /** @param fmt wave file header */ protected abstract Map toProperties(WAVE.fmt fmt); - /** */ - private static final String WAVE_DATA_NOT_LOAD_KEY = "vavi.util.win32.WAVE.data.notLoadData"; - /** * Returns the AudioFileFormat from the given InputStream. Implementation. * @@ -95,8 +94,12 @@ protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLen try { int bufferSize = getBufferSize(); bitStream.mark(bufferSize); - System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "true"); - WAVE wave = Chunk.readFrom(bitStream, WAVE.class); + LimitedInputStream is = new LimitedInputStream(bitStream, bufferSize); + Map context = new HashMap<>(); + context.put(WAVE.CHUNK_PARSE_STRICT_KEY, true); + context.put(WAVE.MULTIPART_CHUNK_PARSE_STRICT_KEY, true); + context.put(WAVE.WAVE_DATA_NOT_LOAD_KEY, true); + WAVE wave = Chunk.readFrom(is, WAVE.class, context); WAVE.fmt fmt = wave.findChildOf(WAVE.fmt.class); int formatCode = fmt.getFormatId(); Debug.println(Level.FINER, "formatCode: " + formatCode); @@ -108,16 +111,26 @@ protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLen channels = fmt.getNumberChannels(); properties = toProperties(fmt); Debug.println(Level.FINER, "properties: " + properties); - bitStream.reset(); - } catch (IOException | IllegalArgumentException e) { + } catch (IOException e) { + if (e.getMessage().equals(LimitedInputStream.ERROR_MESSAGE_REACHED_TO_LIMIT)) { +Debug.println(Level.FINER, e); +Debug.printStackTrace(Level.FINEST, e); + throw (UnsupportedAudioFileException) new UnsupportedAudioFileException(e.getMessage()).initCause(e); + } else { + throw e; + } + } catch (Exception e) { Debug.println(Level.FINER, e); +Debug.printStackTrace(Level.FINEST, e); throw (UnsupportedAudioFileException) new UnsupportedAudioFileException(e.getMessage()).initCause(e); } finally { - System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "false"); try { bitStream.reset(); } catch (IOException e) { - Debug.printStackTrace(e); + if (Debug.isLoggable(Level.FINEST)) +Debug.printStackTrace(e); + else +Debug.println(Level.FINE, e); } Debug.println(Level.FINER, "finally available: " + bitStream.available()); } @@ -171,14 +184,13 @@ public AudioInputStream getAudioInputStream(InputStream stream) throws Unsupport * @throws IOException if an I/O exception occurs. */ protected AudioInputStream getAudioInputStream(InputStream inputStream, int mediaLength) throws UnsupportedAudioFileException, IOException { - try { - AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, mediaLength); - // TODO super cutting corner, should get data position in above method and set it in props and skip here - System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "true"); - WAVE wave = Chunk.readFrom(inputStream, WAVE.class); - return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength()); - } finally { - System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "false"); - } + AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, mediaLength); + // TODO super cutting corner, should get data position in above method and set it in props and skip here + Map context = new HashMap<>(); + context.put(WAVE.CHUNK_PARSE_STRICT_KEY, true); + context.put(WAVE.MULTIPART_CHUNK_PARSE_STRICT_KEY, true); + context.put(WAVE.WAVE_DATA_NOT_LOAD_KEY, true); + WAVE wave = Chunk.readFrom(inputStream, WAVE.class, context); + return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength()); } } diff --git a/src/test/java/vavi/sound/sampled/adpcm/ms/MsWaveAudioFileReaderTest.java b/src/test/java/vavi/sound/sampled/adpcm/ms/MsWaveAudioFileReaderTest.java index 47b93eb..ba5b033 100644 --- a/src/test/java/vavi/sound/sampled/adpcm/ms/MsWaveAudioFileReaderTest.java +++ b/src/test/java/vavi/sound/sampled/adpcm/ms/MsWaveAudioFileReaderTest.java @@ -9,22 +9,27 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.net.URL; import java.nio.ByteOrder; import java.nio.file.Files; +import java.nio.file.Paths; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import vavi.util.Debug; import vavix.util.Checksum; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static vavi.sound.SoundUtil.volume; @@ -92,4 +97,33 @@ public void test1() throws Exception { assertEquals(Checksum.getChecksum(getClass().getResourceAsStream(correctFile)), Checksum.getChecksum(outFile)); } + + @Test + @DisplayName("another input type 2") + void test2() throws Exception { + URL url = Paths.get("src/test/resources/" + inFile).toUri().toURL(); + AudioInputStream ais = AudioSystem.getAudioInputStream(url); + assertEquals(MsEncoding.MS, ais.getFormat().getEncoding()); + } + + @Test + @DisplayName("another input type 3") + void test3() throws Exception { + File file = Paths.get("src/test/resources/" + inFile).toFile(); + AudioInputStream ais = AudioSystem.getAudioInputStream(file); + assertEquals(MsEncoding.MS, ais.getFormat().getEncoding()); + } + + @Test + @DisplayName("when unsupported file coming") + void test5() throws Exception { + InputStream is = MsWaveAudioFileReaderTest.class.getResourceAsStream("/test.caf"); + int available = is.available(); + UnsupportedAudioFileException e = assertThrows(UnsupportedAudioFileException.class, () -> { +Debug.println(is); + AudioSystem.getAudioInputStream(is); + }); +Debug.println(e.getMessage()); + assertEquals(available, is.available()); // spi must not consume input stream even one byte + } } diff --git a/src/test/resources/test.caf b/src/test/resources/test.caf new file mode 100644 index 0000000..2f6c025 Binary files /dev/null and b/src/test/resources/test.caf differ