diff --git a/README.md b/README.md
index ee007df..373d562 100644
--- a/README.md
+++ b/README.md
@@ -5,28 +5,30 @@
# vavi-sound
+
+
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