Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

- name: Set up JDK 17
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '25'
distribution: 'temurin'
cache: maven
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ jobs:
if: ${{ contains(github.event.head_commit.message, 'bump version') }}
run: grep "<version>" pom.xml | head -1 | grep -v SNAPSHOT

- name: Set up JDK 17
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '25'
distribution: 'temurin'
cache: maven
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Release](https://jitpack.io/v/umjammer/mp3spi.svg)](https://jitpack.io/#umjammer/mp3spi)
[![Java CI](https://github.com/umjammer/mp3spi/actions/workflows/maven.yml/badge.svg)](https://github.com/umjammer/mp3spi/actions/workflows/maven.yml)
[![CodeQL](https://github.com/umjammer/mp3spi/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/umjammer/mp3spi/actions/workflows/codeql-analysis.yml)
![Java](https://img.shields.io/badge/Java-17-b07219)
![Java](https://img.shields.io/badge/Java-25-b07219)
[![Parent](https://img.shields.io/badge/Parent-vavi--sound--sandbox-pink)](https://github.com/umjammer/vavi-sound-sandbox)

# MP3SPI
Expand Down Expand Up @@ -48,6 +48,7 @@ Both are in pure Java.
* out source tag parser (use like vavi-util-tag)
* ~~out source version~~
* [jsidplay2:jump3r](https://github.com/umjammer/JSIDPlay2/tree/vavi/jump3r) is the origin of java-lame (replace?)
* wav file reader

----

Expand Down
2 changes: 1 addition & 1 deletion jitpack.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
jdk:
- openjdk17
- openjdk25
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>net.javazoom</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.18</version>
<version>1.9.19</version>

<properties>
<tritonus.groupId>com.github.umjammer.tritonus</tritonus.groupId> <!-- org.tritonus / com.github.umjammer.tritonus -->
Expand Down Expand Up @@ -53,8 +53,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<target>17</target>
<source>17</source>
<target>25</target>
<source>25</source>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.desktop/com.sun.media.sound=ALL-UNNAMED</arg>
Expand Down Expand Up @@ -89,7 +89,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.2</version>
<version>6.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand All @@ -100,7 +100,7 @@
<dependency>
<groupId>com.github.umjammer</groupId> <!-- vavi / com.github.umjammer -->
<artifactId>vavi-sound</artifactId>
<version>1.0.22</version>
<version>1.0.27</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

import javazoom.spi.mpeg.sampled.file.MpegEncoding;
import org.tritonus.share.sampled.Encodings;
Expand All @@ -45,18 +46,33 @@ public class MpegFormatConversionProvider extends TEncodingFormatConversionProvi

private static final Logger logger = getLogger("org.tritonus.TraceAudioConverter");

private static final AudioFormat.Encoding MP3 = Encodings.getEncoding("MP3");
private static final AudioFormat.Encoding PCM_SIGNED = Encodings.getEncoding("PCM_SIGNED");

private static final AudioFormat[] INPUT_FORMATS = {
// mono
new AudioFormat(MP3, NOT_SPECIFIED, NOT_SPECIFIED, 1, NOT_SPECIFIED, NOT_SPECIFIED, false),
new AudioFormat(MP3, NOT_SPECIFIED, NOT_SPECIFIED, 1, NOT_SPECIFIED, NOT_SPECIFIED, true),
// stereo
new AudioFormat(MP3, NOT_SPECIFIED, NOT_SPECIFIED, 2, NOT_SPECIFIED, NOT_SPECIFIED, false),
new AudioFormat(MP3, NOT_SPECIFIED, NOT_SPECIFIED, 2, NOT_SPECIFIED, NOT_SPECIFIED, true),
/**
* The encodings {@link javazoom.spi.mpeg.sampled.file.MpegAudioFileReader} puts into the
* formats it returns. Those are what a source format actually holds, an encoding simply
* named "MP3" is never one of them.
*/
private static final AudioFormat.Encoding[] MPEG_ENCODINGS = {
MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3,
MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3,
MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, MpegEncoding.MPEG2DOT5L3,
};

private static final AudioFormat[] INPUT_FORMATS = inputFormats();

private static AudioFormat[] inputFormats() {
List<AudioFormat> formats = new ArrayList<>();
for (AudioFormat.Encoding encoding : MPEG_ENCODINGS) {
for (int channels : new int[] {1, 2}) {
for (boolean bigEndian : new boolean[] {false, true}) {
formats.add(new AudioFormat(encoding, NOT_SPECIFIED, NOT_SPECIFIED, channels, NOT_SPECIFIED, NOT_SPECIFIED, bigEndian));
}
}
}
return formats.toArray(AudioFormat[]::new);
}

private static final AudioFormat[] OUTPUT_FORMATS = {
// mono, 16 bit signed
new AudioFormat(PCM_SIGNED, NOT_SPECIFIED, 16, 1, 2, NOT_SPECIFIED, false),
Expand All @@ -74,37 +90,84 @@ public MpegFormatConversionProvider() {
logger.log(Level.TRACE, ">MpegFormatConversionProvider()");
}

@Override
public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream):");
return new DecodedMpegAudioInputStream(targetFormat, audioInputStream);
/**
* Whether the given format is an mpeg stream this provider can decode, i.e. one that
* carries everything the decoder needs to size its output.
*/
private static boolean isDecodable(AudioFormat sourceFormat) {
return sourceFormat.getEncoding() instanceof MpegEncoding
&& (sourceFormat.getFrameRate() != NOT_SPECIFIED || sourceFormat.getFrameSize() != NOT_SPECIFIED)
&& sourceFormat.getChannels() != NOT_SPECIFIED
&& sourceFormat.getSampleRate() != NOT_SPECIFIED;
}

/**
* The one pcm format the decoder produces out of the given mpeg stream. It never changes
* the sample rate nor the channel count, and always writes 16 bit signed samples; only
* the endianness is up to the caller.
*/
private static AudioFormat decodedFormat(AudioFormat sourceFormat, boolean bigEndian) {
return new AudioFormat(PCM_SIGNED,
sourceFormat.getSampleRate(),
16,
sourceFormat.getChannels(),
sourceFormat.getChannels() * 2,
sourceFormat.getSampleRate(),
bigEndian);
}

/**
* Add conversion support for any MpegEncoding source with FrameRate or FrameSize not empty.
*
* @param targetFormat
* @param sourceFormat
* @return
* The formats of the super class are matched against the source with
* {@code AudioFormats.matches()}, which cannot express "same sample rate and same channel
* count as the source". They are therefore built from the source format here.
*/
@Override
public boolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat):");
logger.log(Level.TRACE, "checking if conversion possible");
logger.log(Level.TRACE, "from: " + sourceFormat);
logger.log(Level.TRACE, "to: " + targetFormat);

boolean conversion = super.isConversionSupported(targetFormat, sourceFormat);
if (!conversion) {
AudioFormat.Encoding enc = sourceFormat.getEncoding();
if (enc instanceof MpegEncoding) {
if ((sourceFormat.getFrameRate() != AudioSystem.NOT_SPECIFIED) || (sourceFormat.getFrameSize() != AudioSystem.NOT_SPECIFIED)) {
if (sourceFormat.getChannels() == targetFormat.getChannels()) {
conversion = true;
}
}
public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat):");
if (isDecodable(sourceFormat)) {
if (PCM_SIGNED.equals(targetEncoding)) {
return new AudioFormat[] {
decodedFormat(sourceFormat, false),
decodedFormat(sourceFormat, true),
};
}
return new AudioFormat[0];
}
return super.getTargetFormats(targetEncoding, sourceFormat);
}

@Override
public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.getTargetEncodings(AudioFormat sourceFormat):");
if (isDecodable(sourceFormat)) {
return new AudioFormat.Encoding[] {PCM_SIGNED};
}
return super.getTargetEncodings(sourceFormat);
}

@Override
public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream audioInputStream) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream audioInputStream):");
AudioFormat sourceFormat = audioInputStream.getFormat();
if (isDecodable(sourceFormat) && PCM_SIGNED.equals(targetEncoding)) {
// the super class would ask for a target format with every field NOT_SPECIFIED,
// which the decoder cannot size its output buffer from
return getAudioInputStream(decodedFormat(sourceFormat, sourceFormat.isBigEndian()), audioInputStream);
}
return conversion;
return super.getAudioInputStream(targetEncoding, audioInputStream);
}

@Override
public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) {
logger.log(Level.TRACE, ">MpegFormatConversionProvider.getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream):");
AudioFormat sourceFormat = audioInputStream.getFormat();
if (isDecodable(sourceFormat)) {
if (!isConversionSupported(targetFormat, sourceFormat)) {
throw new IllegalArgumentException("unable to convert " + sourceFormat + " to " + targetFormat);
}
// the requested format may leave fields unspecified, the decoder needs them all
return new DecodedMpegAudioInputStream(decodedFormat(sourceFormat, targetFormat.isBigEndian()), audioInputStream);
}
return new DecodedMpegAudioInputStream(targetFormat, audioInputStream);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileEx
// Tell shoucast server (if any) that SPI support shoutcast stream.
conn.setRequestProperty("Icy-Metadata", "1");
InputStream inputStream = conn.getInputStream();
AudioFileFormat audioFileFormat = null;
AudioFileFormat audioFileFormat;
try {
audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes);
} finally {
Expand All @@ -166,9 +166,9 @@ public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileEx
*
* @param inputStream it's user's responsibility to prepare enough read buffer for mp3 tag analysis like
* <pre>
* ... = getAudioFileFormat(new BufferedInputStream(your_input_stream, INITIAL_READ_LENGTH)) ...
* ... = getAudioFileFormat(new BufferedInputStream(your_input_stream, max_file_length - 8 - 1)) ...
* </pre>
* and max buffer size is defined in {@link #INITIAL_READ_LENGTH}.
* and max buffer size is defined by the system property {@code mp3spi.bufferSize}.
* @see #INITIAL_READ_LENGTH
*/
@Override
Expand Down Expand Up @@ -196,7 +196,7 @@ public AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLen
if (!weak) {
if (typeOfFormat != 0x55) throw new UnsupportedAudioFileException("WAV (" + typeOfFormat + ") stream found");
}
pis.skip(22); // TODO sloppy
pis.skipNBytes(22); // TODO sloppy
} else {
if (!weak) throw new UnsupportedAudioFileException("unsupported WAV stream found");
}
Expand Down
Loading
Loading