Trying to play sound files in Java has been unexpectedly challenging. For starters, sound is complex and media formats are also involved. On top of that Java has a couple of ways to play sound many of which are a bit verbose.
I thought that I could use JavaFX. The API seemed pretty good but it has a key flaw - the design of JavaFX uses a hardcoded version of libav, so every time Debian upgrades libav sound on my projects stops working; even if I use the system JavaFX library. It is actually worse than that because sometimes the system library is too new for JavaFX so there isn’t even a released version compatible with Debian’s libav version. That was for mp3s, today I want to play an OGG file and it is time to try something new and hopefully more reliable.
ChatGPT reccommended tritonus
but that seems to be a red herring. I had a great deal of difficulty
figuring out the dependencies and it wasn’t useful compared to
(JavaZoom’s?) vorbisspi
. vorbisspi
seemed to
be necessary when I was trying to set things up, and has appropriate
dependencies on tritonus and it’s jorbis plugin.
OpenJDK v. 21 on Debian (GNU/Linux trixie/sid).
I installed the tritonus system libraries as an investigative exercise to help me understand what artefacts I would be looking for. In the end I decided to re-download these from maven because adding system libraries has not been a great experience in the past.
$ sudo apt install libtritonus-java libvorbisspi-java
Installing:
libtritonus-java
Installing dependencies:
libtritonus-jni
...
$ dpkg -L libtritonus-java
...
/usr/share/java/tritonus_vorbis.jar
...
I continue to recommend leiningen for new Clojure projects.
Added in project.clj
.
;; Added in defproject / :dependencies
;; This adds Vorbis support to the JVM transparently.
[com.googlecode.soundlibs/vorbisspi "1.0.3.3"]
Today I am using a file from Freesound. Load Drum 1.ogg by
hello_flowers – https://freesound.org/s/33823/ – which I renamed to
load-drum-1.ogg
. I put it into the resources
folder of my project. The project structure looks something like
this:
$ tree
.
├── project.clj
├── README.md
├── resources
│ └── load-drum-1.ogg
└── src
└── project
└── core.clj
Added to core.clj
.
(require '[clojure.java.io :as io])
(import '[javax.sound.sampled AudioSystem AudioFormat AudioFormat$Encoding])
(defn ->pcm-signed-format
[vorbis-audio-stream]
(let [format
(.getFormat vorbis-audio-stream)]
(AudioFormat. AudioFormat$Encoding/PCM_SIGNED
(.getSampleRate format) 16 (.getChannels format) (* 2 (.getChannels format)) (.getSampleRate format) false)))
(defn play-ogg
[ogg-resource-name]
(let [audio-stream (AudioSystem/getAudioInputStream (io/resource ogg-resource-name))
rs (AudioSystem/getAudioInputStream (->pcm-signed-format audio-stream) audio-stream)]
(doto (AudioSystem/getClip)
(.open rs)
(.start))))
(play-ogg "load-drum-1.ogg")
The first effort was a lot simpler. But there were two major errors that needed to be resolved that ended up making the code heavier.
The first indicated that the dependencies were set up wrongly and
that was when I switched from a direct dependency on
tritonus
to vorbisspi
.
Execution error (UnsupportedAudioFileException) at javax.sound.sampled.AudioSystem/getAudioInputStream (AudioSystem.java:1038).
URL of unsupported format
The second error I’m a bit less certain about what is happening. The
libraries involved are opening the OGG file without much trouble but as
an encoded stream. Apparently (ChatGPT again) this suggests something is
going wrong, possibly linked to tritonus-jorbis
. The
VorbisSPI should be decoding the audio stream somewhere in the plumbing.
But it didn’t, so the format had to be adjusted manually.
Execution error (LineUnavailableException) at com.sun.media.sound.DirectAudioDevice$DirectDL/implOpen (DirectAudioDevice.java:484).
line with format VORBISENC 44100.0 Hz, unknown bits per sample, mono, 1 bytes/frame, 7500.0 frames/second not supported.
I had a poke at this error without making real progress. I tried
looking at the project dependencies and noted that there was no
tritonus-jorbis
artefact (which is probably the problem).
But I couldn’t find a tritonus-jorbis
to add (I tried
tritonus-all
from the Google Sound repo, but that didn’t
fix anything).
$ lein deps :tree
[com.googlecode.soundlibs/vorbisspi "1.0.3.3"]
[com.googlecode.soundlibs/jorbis "0.0.17.4"]
[com.googlecode.soundlibs/tritonus-share "0.3.7.4"]
Since I had the libraries, I tried switching to using the local .jar
files. That led to … a libogg
segfault!
;; project.clj
:resource-paths ["resources"
"/usr/share/java/vorbisspi.jar"
"/usr/share/java/tritonus_alsa.jar"
"/usr/share/java/tritonus_aos.jar"
"/usr/share/java/tritonus_cdda.jar"
"/usr/share/java/tritonus_core.jar"
"/usr/share/java/tritonus_dsp.jar"
"/usr/share/java/tritonus_esd.jar"
"/usr/share/java/tritonus_fluidsynth.jar"
"/usr/share/java/tritonus_gsm.jar"
"/usr/share/java/tritonus_javasequencer.jar"
"/usr/share/java/tritonus_jorbis.jar"
"/usr/share/java/tritonus_mp3.jar"
"/usr/share/java/tritonus_pvorbis.jar"
"/usr/share/java/tritonus_remaining.jar"
"/usr/share/java/tritonus_share.jar"
"/usr/share/java/tritonus_src.jar"
"/usr/share/java/tritonus_vorbis.jar"]
$ lein repl
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fee53a110dd, pid=26336, tid=26341
#
# JRE version: OpenJDK Runtime Environment (21.0.5+11) (build 21.0.5+11-Debian-1)
# Java VM: OpenJDK 64-Bit Server VM (21.0.5+11-Debian-1, mixed mode, emulated-client, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
# Problematic frame:
# C [libogg.so.0+0x30dd] ogg_sync_init+0xd
#
Looks like libtritonus-java is doing something wrong, a gentle probe
of the source code (apt source libogg libtritonus-jni
)
makes me think that tritonus is passing an invalid handle into libogg.
Looks harder to debug than just living with manually converting the
audio format.
This isn’t a very satisfying conclusion; we have an awkward workaround to a broken dependency and it looks like there might be a bug in Debian’s libtritonus-java package. Or I might be using it wrong. Still, hopefully this approach is more robust than using JavaFX!