This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Follow these practices for all non-trivial work in this repository.
Before writing any code, think through the approach. For any task beyond a trivial fix:
- Draft a plan — identify the files to change, the interfaces to add/modify, and the test cases needed.
- Review the plan against the constraints in this file.
- Only start editing once the plan is coherent. A bad plan caught early is far cheaper than a bad implementation caught late.
When adding new functionality, write the failing test before writing the implementation. A failing test creates an immediate, objective feedback loop. This dramatically improves reliability — run the test after every meaningful change to stay oriented.
Prefer many small, verifiable steps over one large change. Each increment should:
- Leave the codebase in a working state (tests pass, linter clean).
- Be independently reviewable.
If a task feels too large to hold in one session, decompose it further.
A Spotless formatter is configured for all Java modules. It runs automatically during install/verify, but a failed format check blocks the build. After editing Java sources run:
mvn spotless:apply
# or for a single module:
mvn spotless:apply -pl ffsampledsp-java
mvn spotless:apply -pl ffsampledsp-completeFFSampledSP is a Java/JNI library that implements javax.sound.sampled service provider interfaces (SPIs) backed by FFmpeg. It decodes audio files/streams to PCM — signed integer (PCM_SIGNED) or floating-point (PCM_FLOAT). Licensed under LGPL 2.1. Requires Java 8 or later.
The build requires Maven 3.6+, a JDK 8+, and Doxygen. Native compilation requires platform-specific toolchains. A platform profile must be activated — native modules are not built by default.
# Build and test for aarch64 (Apple Silicon)
mvn --activate-profiles ffsampledsp-aarch64-macos install
# Build and test for x86_64
mvn --activate-profiles ffsampledsp-x86_64-macos installmvn --activate-profiles ffsampledsp-x86_64-linux install
# For aarch64 cross-compile (requires aarch64-linux-gnu-gcc, tests are skipped):
mvn --activate-profiles ffsampledsp-aarch64-linux installmvn --activate-profiles ffsampledsp-x86_64-win install # 64-bit
mvn --activate-profiles ffsampledsp-i386-win install # 32-bit (tests skipped)mvn install # builds ffsampledsp-java and ffsampledsp-complete onlyTests live in ffsampledsp-complete/src/test/java/. They require the native library to be built first (via a platform profile).
# Run all tests (after native build)
mvn --activate-profiles ffsampledsp-aarch64-macos test
# Run a single test class
mvn --activate-profiles ffsampledsp-aarch64-macos test \
-pl ffsampledsp-complete \
-Dtest=TestFFAudioFileReader
# Run specific test methods
mvn --activate-profiles ffsampledsp-aarch64-macos test \
-pl ffsampledsp-complete \
-Dtest="TestFFCodecInputStream#testConvertWavToFloat32SamplesInRange"Pass -Dcflags=-DDEBUG to enable C-level debug output to stdout.
ffsampledsp-java/— Pure Java SPI implementations. The authoritative Java source; compiled standalone but also copied intoffsampledsp-completeat build time.ffsampledsp-x86_64-macos/— Canonical C source module. All C sources live here; all other platform modules reference the samesrc/main/c/directory via their pom.ffsampledsp-{arch}-{host}/— Per-platform native modules (aarch64-macos,x86_64-linux,aarch64-linux,x86_64-win,i386-win). Each packages a.dylib/.so/.dllbuilt from the canonical C sources.ffsampledsp-complete/— The distribution artifact. Copies Java sources fromffsampledsp-javaand embeds the native library from whichever platform profile is active. This is the jar users depend on.
The Java classes in com.tagtraum.ffsampledsp implement the javax.sound.sampled.spi interfaces:
FFAudioFileReader— implementsAudioFileReader. Opens URLs/files/streams via FFmpeg. Caches results (LRU, 20 entries). Has agetAudioFileFormats()extension returning multiple formats for multi-stream files (e.g. Stems). The three-argument overloadsgetAudioInputStream(file/url, streamIndex, fileBufferSize)accept an explicit I/O buffer size. Calls into native via twonativemethods:getAudioFileFormatsFromURLandgetAudioFileFormatsFromBuffer.FFFormatConversionProvider— implementsAudioFormatConversionProvider. Transcodes compressed streams to PCM (PCM_SIGNED,PCM_UNSIGNED,PCM_FLOAT). StandardAudioFormat.Encodingconstants (e.g.AudioFormat.Encoding.PCM_FLOAT) work interchangeably withFFAudioFormat.FFEncodingvalues via string-based resolution.getAudioInputStream(Encoding, AudioInputStream)defaults to 32-bit forPCM_FLOAT(never inherits a sub-32-bit source depth).FFNativePeerInputStream— abstract base for native-backedInputStreams. Holds along pointerto the native C struct and a directByteBuffer(nativeBuffer) that the C side fills.FFURLInputStream— decodes from a URL/file path. Configures the FFmpeg I/O buffer size (AVFormatContext.io_buffer_size) passed asfileBufferSize. Default forfile:URLs: 1 MB (override with-Dffsampledsp.fileBufferSize=N). Default for other URLs: 64 KB (override with-Dffsampledsp.urlBufferSize=N).FFStreamInputStream— decodes from a JavaInputStream(reads into a buffer, probes format, then decodes).FFCodecInputStream— handles format conversion (resampling/channel mapping/sample-format) usinglibswresample. SupportsPCM_SIGNED(8/16/24/32-bit),PCM_UNSIGNED(8/16/24/32-bit), andPCM_FLOAT(32-bit and 64-bit). Float output is normalized to[-1, 1]by libswresample for integer sources; lossy codecs (MP3, AAC) may produce inter-sample peaks slightly outside this range.
FFAudioFormat— definesFFEncoding(extendsAudioFormat.Encoding) and theCodecenum. All float codec variants (PCM_F32LE/BE,PCM_F64LE/BE, etc.) useEncoding.PCM_FLOAT.toString()as their encoding name — no hardcoded string constant.FFAudioInputStream— wraps anFFNativePeerInputStream, implements seeking viaFFGlobalLock.FFGlobalLock— a singleReentrantLock(LOCK) used to serialize FFmpeg calls that are not thread-safe (avcodec_open2, etc.).FFNativeLibraryLoader— extracts the embedded native library tojava.io.tmpdirand loads it. Naming convention:ffsampledsp-{arch}-{host}.{ext}(e.g.ffsampledsp-aarch64-macos.dylib).
All native sources live in one directory — all platforms share them: ffsampledsp-x86_64-macos/src/main/c/
Java language/compiler target is release=8, set in the root pom.xml.
FFUtils.c/FFUtils.h— shared helpers: JNI field/method ID caching, buffer management, FFmpeg context lifecycle, DRM detection (CODEC_TAG_DRMS). Minimum probe score of 5 prevents misdetecting files that otherjavax.sound.sampledproviders should handle.FFAudioFileReader.c— native implementation of the twoFFAudioFileReadernative methods. Probes format, fills JavaFFAudioFileFormat/FFAudioFormatobjects.FFURLInputStream.c— opens anAVFormatContextfrom a URL, configuresAVFormatContext.io_buffer_sizefrom the Java-sidefileBufferSize, decodes packets into the JavanativeBuffer.FFStreamInputStream.c— uses FFmpeg's custom I/O (AVIOContextwith read callbacks) to pull data from a JavaInputStream.FFCodecInputStream.c— wrapslibswresamplefor PCM conversion. Output sample format is selected from the Java-sideAudioFormat:AV_SAMPLE_FMT_S16for 16-bit signed,AV_SAMPLE_FMT_FLTfor 32-bit float,AV_SAMPLE_FMT_DBLfor 64-bit float, etc. Uses the modernav_opt_set_*()API (not the deprecatedswr_alloc_set_opts()).
- The native library is embedded inside
ffsampledsp-complete.jarand extracted to a temp file on first load. The extracted filename includes the version to allow side-by-side installs; SNAPSHOT builds are always re-extracted. - All calls touching FFmpeg's non-thread-safe API are wrapped in
FFGlobalLock.LOCK. - Windows URLs require a special format for libav:
file:C:/path/file(notfile:///C:/path/file). UNC paths usefile://server/path. This conversion happens inFFAudioFileReader.urlToString(). - The
fileToURLmethod explicitly decodes then re-encodes file URIs to preserve+characters in paths (a known edge case). - JNI headers are auto-generated by
javac -hduring thecompilephase intotarget/native/include/, then consumed by the platform-specific native module. FFCodecInputStreamuses a((Buffer) nativeBuffer).limit(0)cast to work around the covariantByteBuffer.limit(int)return type introduced in Java 9.PCM_FLOATsupport works with the standardAudioFormat.Encoding.PCM_FLOATconstant (Java 7+) because all provider methods resolve encodings by callingFFAudioFormat.FFEncoding.getInstance(encoding.toString()).