Didn't find class "javax.sound.sampled.AudioSystem" in Android Studio

I’ve successfully integrated Processing with Android Studio (see thread). I’ve added sound.jar and jsyn-17.1.0.jar libraries for my SoundFile codes. Now I’m stuck with:

FATAL EXCEPTION: Animation Thread
	    Process: androidstudio.audiotest, PID: 25715
	    java.lang.NoClassDefFoundError: Failed resolution of: Ljavax/sound/sampled/AudioSystem;
		at processing.sound.SoundFile.<init>(Unknown Source:59)
		at processing.sound.SoundFile.<init>(Unknown Source:1)
		at androidstudio.audiotest.Sketch.setup(Sketch.java:31)
		at processing.core.PApplet.handleDraw(PApplet.java:1878)
		at processing.core.PSurfaceNone.callDraw(PSurfaceNone.java:477)
		at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:517)
	    Caused by: java.lang.ClassNotFoundException: Didn't find class "javax.sound.sampled.AudioSystem" on path: DexPathList[[zip file "/data/app/~~KS0b9YLzfUp2XIUvg_XC4g==/androidstudio.audiotest-xSWtOj9LfvyMJDB4_flMXw==/base.apk"],nativeLibraryDirectories=[/data/app/~~KS0b9YLzfUp2XIUvg_XC4g==/androidstudio.audiotest-xSWtOj9LfvyMJDB4_flMXw==/lib/arm64, /system/lib64, /system/system_ext/lib64]]
		at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
		at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
		at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
		at processing.sound.SoundFile.<init>(Unknown Source:59) 
		at processing.sound.SoundFile.<init>(Unknown Source:1) 
		at androidstudio.audiotest.Sketch.setup(Sketch.java:31) 
		at processing.core.PApplet.handleDraw(PApplet.java:1878) 
		at processing.core.PSurfaceNone.callDraw(PSurfaceNone.java:477) 
		at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:517) 

According to this thread, I have to use something else. Any advice? Thanks.

I found a ported jar-file. I tried to add like the sound.jar, but no luck. Any help would be appreciated.

Hi @bigboss97. I assume you’re using Processing’s sound library? It’s looking for the Java Sound API which doesn’t work on Android.
Maybe @kevinstadler has some advice on how to setup the library to be used in Android Studio.
Btw, it looks like Android mode for Windows has been fixed. :grinning:

Hello,

indeed there is an Android port of the official Sound library that should just work out of the box, you can find it at the bottom of the release page under “sound-android.zip”: Release v2.4.0 · processing/processing-sound · GitHub
@bigboss97 you can just extract the content of the zip into your Processing Installation’s libraries folder, replacing the sound/ directory that is already in there.
Let me know if there are any problems, I’ll make sure to put the Android installation instructions more visibly on the sound library page!

Thanks for the responses.
I’ve downloaded sound-android.zip and tried on Android Studio. Since I’m getting error when I link library directory I did it one by one. I got the same error when I attached 5 jars:

I got duplicate classes when I applied all:

When I removed jsyn-17.1.0.jar I got:

Caused by: java.lang.ClassNotFoundException: Didn't find class "com.jsyn.devices.AudioDeviceManager"

I did use jsyn-17.1.0.jar to solve the problem. But then got:
Didn’t find class “javax.sound.sampled.AudioSystem”

According to javax-sound-library-for-android, the jar can be used by import mg.dida.* which means the processing-sound.jar has to be re-compiled with the import changes.
I’ve gone through the source files. I believe there are only two files to be modified:
Engine.java
SoundFile.java

I don’t know how to re-generate the sound.jar with the ported javax.sound jar. @kevinstadler, would you be able to give me some guides? Thanks.

It really shouldn’t be necessary to add any extra jars or fiddle with imports/porting, if anything there is actually a risk of stopping it from working normally by including redundant jars or other libraries! My suggestion would be to first remove all sound libraries from your Processing installation, and then add only the contents of sound-android.zip.

I don’t know how exactly Android mode dependencies work, but I see that in your screenshot above the library files are in a sound-android folder, so I guess the original (non-Android) library is still in the sound folder – I would delete it just in case, as well as the javax-sound port (including that might stop the Sound library from detecting that it is on Android, and thus stop it from switching to Android media sound output).

Hi @bigboss97. If you are trying to run just the Processing Sound library with Android Studio you should only need to include the sound.jar and jsyn-17.0.1.jar files in your /libs folder and then add this line your app’s build.gradle (or build.gradle.kts). (You should already have the processing-core.jar implementation in this file.)

implementation(fileTree(mapOf(
        "dir" to "C:\\Users\\***\\Documents\\androidtest\\app\\libs\\sound.jar",
        "include" to listOf("*.aar", "*.jar")
    )))
implementation(fileTree(mapOf(
        "dir" to "C:\\Users\\***\\Documents\\androidtest\\app\\libs\\jsyn17.0.1.jar",
        "include" to listOf("*.aar", "*.jar")
    )))

OK, I’ve done some “quick” & dirty works :blush:
I removed the sound.jar and instead I added the SoundFile.java and its dependent classes directly into my project. I modified them to use the Android Studio ported javax.sound.*:

import mg.dida.javax.sound.share.classes.javax.sound.sampled*

Hooray, I managed to get FFT works on Android Studio, but… it only works with wav-file. When I tried it with MP3 it failed with UnsupportedAudioFileException in SoundFile.java

			try {
				// load WAV or AIF using JSyn
				this.sample = SampleLoader.loadFloatSample(fin);
			} catch (IOException e) {
				// not wav/aiff -- try converting via JavaSound...
				try {
					// stream was modified by first read attempt, so re-create it
					AudioInputStream in = AudioSystem.getAudioInputStream(parent.createInput(path));
...
				} catch (UnsupportedAudioFileException ee) {
					throw new RuntimeException(ee);
				}

This came from the routine in ported javax.sound:

	public static AudioInputStream getAudioInputStream(InputStream stream)
			throws UnsupportedAudioFileException, IOException {

		List providers = getAudioFileReaders();
		AudioInputStream audioStream = null;

		for(int i = 0; i < providers.size(); i++ ) {
			AudioFileReader reader = (AudioFileReader) providers.get(i);
			try {
				audioStream = reader.getAudioInputStream( stream ); // throws IOException
				break;
			} catch (UnsupportedAudioFileException e) {
				continue;
			}
		}

		if( audioStream==null ) {
			throw new UnsupportedAudioFileException("could not get audio input stream from input stream");
		} else {
			return audioStream;
		}
	}

I have no ideas what I should do. If someone can tell me what I could try in getAudioInputStream() that would be great. Thanks in advance.

Hi @bigboss97, Jsyn doesn’t directly support mp3 decoding. In the SampleLoader.java file it states that:

“The default loader uses custom code to load WAV or AIF files. Supported data formats are 16, 24 and 32 bit PCM, and 32-bit float. Compressed formats such as unsigned 8-bit, uLaw, A-Law and MP3 are not support. (sic) If you need to load one of those files try setJavaSoundPreferred(true). Or convert it to a supported format using Audacity or Sox or some other sample file tool.”
So you could try: setJavaSoundPreferred(true). but the code comments warn that it doesn’t work on Android.
If you are just trying to playback an mp3 file or other compressed audio formats, then Android provides that capability in JetPack Media3.
But if you want to do anything else, like analysis with FFT, the audio file will have to be decoded into the sample data (PCM).
Best option is to just work with .wav files.
But if you need smaller file sizes you can look at:
MediaCodec | Android Developers

Since FFT is working for WAV I don’t mind to convert each MP3 to a temporary WAV before play. I’ve looked at AndroidAudioConverter. I’m getting errors when I added the dependencie to app/build.gradle.kts:

app/build.gradle.kts:52:13: Unexpected tokens (use ';' to separate expressions on the same line)

I’m not familiar with Android Studio at all.
At the moment, I would be happy if I can find a simple way to convert MP3 to WAV file on demand. I found converting-mp3-to-pcm. But it’s for Kotlin only :frowning_face:

Can you share the code from your build script so we can see what might be causing the error? It’s probably just a syntax issue, but without the actual code we can’t help you.

I’ve fixed that by updating app/build.cradle:

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }

I have also added some Kotlin codes to convert MP3 to WAV:

package androidstudio.audiotest

import android.media.MediaExtractor
import android.media.MediaFormat
import android.media.MediaCodec
import java.io.File
import java.io.FileOutputStream

class AudioConverter {

    fun mp3ToWav(mp3FilePath: String, wavFilePath: String): String {
        if( !mp3FilePath.lowercase().endsWith(".mp3"))
            return mp3FilePath

        val extractor = MediaExtractor()
        extractor.setDataSource(mp3FilePath)
        val format = extractor.getTrackFormat(0)
        extractor.selectTrack(0)

        val codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!)
        codec.configure(format, null, null, 0)
        codec.start()

        val outputBufferInfo = MediaCodec.BufferInfo()
        val outputFile = File(wavFilePath)
        val outputStream = FileOutputStream(outputFile)
        val outputTmpFile = File(wavFilePath+".tmp")
        val outputTmpStream = FileOutputStream(outputTmpFile)

//        val wavHeader = createWavHeader(format)
//        outputStream.write(wavHeader)

        var numSamples = 0  // Required in header
        while (true) {
            val inputBufferIndex = codec.dequeueInputBuffer(10000)
            if (inputBufferIndex >= 0) {
                val inputBuffer = codec.getInputBuffer(inputBufferIndex)!!
                val sampleSize = extractor.readSampleData(inputBuffer, 0)
                numSamples += outputBufferInfo.size / 2 // Assuming 16-bit PCM
                if (sampleSize < 0) {
                    codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                    break
                } else {
                    codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.sampleTime, 0)
                    extractor.advance()
                }
            }

            val outputBufferIndex = codec.dequeueOutputBuffer(outputBufferInfo, 10000)
            if (outputBufferIndex >= 0) {
                val outputBuffer = codec.getOutputBuffer(outputBufferIndex)!!
                val chunk = ByteArray(outputBufferInfo.size)
                outputBuffer.get(chunk)
                outputTmpStream.write(chunk)
                codec.releaseOutputBuffer(outputBufferIndex, false)
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // Format changed, handle if needed
            }
        }

        codec.stop()
        codec.release()
        extractor.release()
        outputTmpStream.close()

        val wavHeader = createWavHeader(format, numSamples)
        outputStream.write(wavHeader)
        outputStream.close()
        appendFile( outputFile, outputTmpFile)
        return wavFilePath
    }

    fun createWavHeader(format: MediaFormat, numSamples: Int): ByteArray {
        val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
        val channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
        val bitsPerSample = 16 // Assuming 16 bits per sample
        val byteRate = sampleRate * channels * bitsPerSample / 8
        val subChunk2Size = numSamples * channels * bitsPerSample / 8

        val header = ByteArray(44)
        header[0] = 'R'.toByte()
        header[1] = 'I'.toByte()
        header[2] = 'F'.toByte()
        header[3] = 'F'.toByte()
        header[4] = (36 + subChunk2Size).toByte()
        header[5] = ((36 + subChunk2Size) shr 8).toByte()
        header[6] = ((36 + subChunk2Size) shr 16).toByte()
        header[7] = ((36 + subChunk2Size) shr 24).toByte()
        header[8] = 'W'.toByte()
        header[9] = 'A'.toByte()
        header[10] = 'V'.toByte()
        header[11] = 'E'.toByte()
        header[12] = 'f'.toByte()
        header[13] = 'm'.toByte()
        header[14] = 't'.toByte()
        header[15] = ' '.toByte()
        header[16] = 16.toByte()
        header[17] = 0
        header[18] = 0
        header[19] = 0
        header[20] = 1.toByte()
        header[21] = 0
        header[22] = channels.toByte()
        header[23] = 0
        header[24] = (sampleRate and 0xff).toByte()
        header[25] = ((sampleRate shr 8) and 0xff).toByte()
        header[26] = ((sampleRate shr 16) and 0xff).toByte()
        header[27] = ((sampleRate shr 24) and 0xff).toByte()
        header[28] = (byteRate and 0xff).toByte()
        header[29] = ((byteRate shr 8) and 0xff).toByte()
        header[30] = ((byteRate shr 16) and 0xff).toByte()
        header[31] = ((byteRate shr 24) and 0xff).toByte()
        header[32] = (channels * 2).toByte()
        header[33] = 0
        header[34] = 16.toByte()
        header[35] = 0
        header[36] = 'd'.toByte()
        header[37] = 'a'.toByte()
        header[38] = 't'.toByte()
        header[39] = 'a'.toByte()
        header[40] = (subChunk2Size and 0xff).toByte()
        header[41] = ((subChunk2Size shr 8) and 0xff).toByte()
        header[42] = ((subChunk2Size shr 16) and 0xff).toByte()
        header[43] = ((subChunk2Size shr 24) and 0xff).toByte()
        // Remaining header fields...

        return header
    }

    fun appendFile(targetFile: File, fileToAppend: File) {
        // Read contents of the file to append as byte array
        val fileToAppendBytes = fileToAppend.readBytes()

        // Write contents to the target file in append mode
        FileOutputStream(targetFile, true).use { outputStream ->
            outputStream.write(fileToAppendBytes)
        }
    }
}

The above code mainly came from Copilot :blush:
Now I’m able to (slowly) covert a file. But that WAV-file is rejected by SoundFile() although it can be played by VLC. I must have put something in the header that

try {
	// load WAV or AIF using JSyn
	this.sample = SampleLoader.loadFloatSample(fin);
}

doesn’t like.

An example of my WAV-file:

Channels : 2
Sample Rate : 44100
Precision : 16-bit
Duration : 00:06:01.46 = 15940318 samples = 27109.4 CDDA sectors
File Size : 31.9M
Bit Rate : 706k
Sample Encoding: 16-bit Signed Integer PCM
 
File Detail
sox WARN wav: Premature EOF on .wav input file
Samples read: 15940318
Length (seconds): 180.729229
Scaled by: 2147483647.0
Maximum amplitude: 0.744537
Minimum amplitude: -0.818787
Midline amplitude: -0.037125
Mean norm: 0.101872
Mean amplitude: -0.001055
RMS amplitude: 0.136874
Maximum delta: 0.774628
Minimum delta: 0.000000
Mean delta: 0.055206
RMS delta: 0.074749
Rough frequency: 3833
Volume adjustment: 1.221
 
Channel Detail
sox WARN wav: Premature EOF on .wav input file
Overall Left Right
DC offset -0.001785 -0.001785 -0.000326
Min level -0.818787 -0.818787 -0.800537
Max level 0.744537 0.744537 0.715088
Pk lev dB -1.74 -1.74 -1.93
RMS lev dB -17.27 -17.08 -17.48
RMS Pk dB -9.35 -9.35 -9.85
RMS Tr dB -222.46 -218.75 -222.46
Crest factor - 5.85 5.99
Flat factor 0.00 0.00 0.00
Pk count 2 2 2
Bit-depth 16/16 16/16 16/16
Num samples 7.97M
Length s 180.729
Scale max 1.000000
Window s 0.050

Hooray :partying_face:, now FFT works for MP3 (after conversion), too.
Based on the message of the WAV analyzer result:

File Detail
sox WARN wav: Premature EOF on .wav input file

I managed to find the error in my WAV header by using a HEX editor. So I changed following in my Kotlin code:

//        val subChunk2Size = numSamples * channels * bitsPerSample / 8
        val subChunk2Size = numSamples * channels