Class WavAudioSourceProvider

java.lang.Object
com.tino1b2be.dtmf.io.wav.WavAudioSourceProvider
All Implemented Interfaces:
com.tino1b2be.dtmf.io.AudioSourceProvider

public final class WavAudioSourceProvider extends Object implements com.tino1b2be.dtmf.io.AudioSourceProvider
AudioSourceProvider implementation for the RIFF/WAVE (and RF64/WAVE) container formats. This is the public entry point of the dtmf-io-wav module, discovered by ServiceLoader through the META-INF/services/com.tino1b2be.dtmf.io.AudioSourceProvider registration (Requirement 9.2) and normally invoked indirectly via AudioSources.open(...).

Design

The provider is a clean-room RIFF parser: it reads the container byte-by-byte through RiffReader and decodes samples through the shared com.tino1b2be.dtmf.io.internal.SampleConversion helper. It does not import anything under javax.sound.sampled (Requirement 9.12) and ships with zero external runtime dependencies beyond dtmf-io itself. The actual frame-level decoding lives inside WavAudioSource (returned from open(Path) and open(InputStream, String)); this class is exclusively responsible for detecting a WAV input and parsing its header.

Detection (canOpen)

Both canOpen overloads check the same twelve-byte magic pattern (Requirements 9.5, 9.6): bytes 0..3 must be "RIFF" or "RF64", bytes 4..7 are the top-level size field and are not validated at this stage, and bytes 8..11 must be "WAVE". A match returns a score of 100; anything else, including an input shorter than twelve bytes, returns -1. The Path overload opens a fresh FileChannel and closes it before returning; the InputStream overload uses InputStream.mark(int) and InputStream.reset() so the caller's stream is left positioned exactly where it started (Requirement 4.6). Non-markable streams are declined with -1 without consuming any bytes, because reading a header from a non-markable stream would leave it in a state no downstream provider could recover from (Requirement 4.7); the AudioSources facade wraps non-markable inputs in a BufferedInputStream before scoring, so this branch mostly protects direct callers.

Full parse (open)

When a caller proceeds to open(Path) or open(InputStream, String), the provider walks the RIFF form with RiffReader, skipping any LIST / bext / junk / PEAK / unknown chunks and locating the mandatory fmt and data chunks (Requirement 9.11). RF64 files additionally require a ds64 chunk before the fmt chunk: the outer 32-bit size fields are pinned to 0xFFFFFFFF and the real 64-bit sizes are pulled from ds64's riffSize64 and dataSize64. The parser recognises exactly three wFormatTag values (Requirements 9.7, 9.8, 9.9):
  • 0x0001 WAVE_FORMAT_PCM — signed integer PCM at 16, 24, or 32 bits.
  • 0x0003 WAVE_FORMAT_IEEE_FLOAT — IEEE 754 float at 32 or 64 bits.
  • 0xFFFE WAVE_FORMAT_EXTENSIBLE — dispatches on the 16-byte SubFormat GUID; only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT are accepted.
Every other wFormatTag (including 0x0006 A-law, 0x0007 µ-law, 0x0011 IMA ADPCM) is rejected with UnsupportedAudioFormatException identifying the code (Requirement 9.10). Structural defects — a missing fmt , a missing data, a bogus outer magic, or a chunk whose declared size runs off the end of the form — are rejected with IOException describing the defect (Requirement 9.11), so callers can tell "not a valid WAV" from "valid WAV with a compression we do not support" (Requirement 12.4).

Stream ownership

The open(Path) branch opens a FileChannel that the returned AudioSource owns and closes on AudioSource.close(). The open(InputStream, String) branch never closes the caller's stream (Requirement 4.10); it returns a stream-backed WavAudioSource whose AudioSource.close() transitions the source into the closed state but leaves the caller's InputStream untouched.
Since:
2.1.0
See Also:
  • WavAudioSource
  • AudioSourceProvider
  • AudioSource
  • Constructor Details

    • WavAudioSourceProvider

      public WavAudioSourceProvider()
      ServiceLoader requires a public no-argument constructor (Requirement 4.1). Instances are stateless and cheap to construct; the provider caches no data across calls.
  • Method Details

    • formatName

      public String formatName()
      Specified by:
      formatName in interface com.tino1b2be.dtmf.io.AudioSourceProvider
    • priority

      public int priority()
      Specified by:
      priority in interface com.tino1b2be.dtmf.io.AudioSourceProvider
    • canOpen

      public int canOpen(Path path) throws IOException

      Opens a read-only FileChannel on path, reads the first twelve bytes, and returns 100 when they match the RIFF/WAVE or RF64/WAVE pattern (Requirements 9.5, 9.6). The channel is closed via try-with-resources before returning so the detection call does not leak a file handle.

      Any IOException raised while opening or reading the file propagates to the caller; com.tino1b2be.dtmf.io.AudioSources catches such exceptions during scoring, records the provider as having returned -1, logs a warning, and continues.

      Specified by:
      canOpen in interface com.tino1b2be.dtmf.io.AudioSourceProvider
      Parameters:
      path - file to score; must be non-null
      Returns:
      100 on a magic-byte match, -1 otherwise
      Throws:
      NullPointerException - if path is null
      IOException - on I/O failure while reading the file
    • canOpen

      public int canOpen(InputStream stream, String hint) throws IOException

      When stream supports mark/reset, marks up to OUTER_HEADER_BYTES bytes, reads exactly twelve bytes via InputStream.readNBytes(int), scores against the RIFF pattern, and resets the stream in a finally block so the caller's position is restored on both the success and failure paths (Requirement 4.6). Short reads (fewer than twelve bytes) return -1.

      Non-markable streams are declined with -1 without consuming any bytes (Requirement 4.7); the AudioSources facade wraps such streams in a BufferedInputStream before scoring, so in normal use this branch is defensive.

      Specified by:
      canOpen in interface com.tino1b2be.dtmf.io.AudioSourceProvider
      Parameters:
      stream - the stream to score; must be non-null
      hint - optional caller-supplied hint; may be null and is ignored by this provider (content-based detection)
      Returns:
      100 on a magic-byte match, -1 otherwise
      Throws:
      NullPointerException - if stream is null
      IOException - on I/O failure while reading the header prefix
    • open

      public com.tino1b2be.dtmf.io.AudioSource open(Path path) throws IOException

      Opens a read-only FileChannel and drives the RIFF parser over it to build a WaveFormat, then hands the channel (still open, positioned at the first byte of the data payload) to WavAudioSource.fromChannel(WaveFormat, FileChannel). The returned source owns the channel; its AudioSource.close() closes it.

      On any parse failure this method closes the channel before re-throwing so partially-parsed files do not leak file handles.

      Specified by:
      open in interface com.tino1b2be.dtmf.io.AudioSourceProvider
      Parameters:
      path - file to open; must be non-null
      Returns:
      an opened WavAudioSource in channel-backed mode
      Throws:
      NullPointerException - if path is null
      com.tino1b2be.dtmf.io.UnsupportedAudioFormatException - if the file's magic matched but the fmt chunk declares an unsupported encoding (Requirement 9.10)
      IOException - on any other failure, including structural defects (Requirement 9.11) and underlying I/O errors
    • open

      public com.tino1b2be.dtmf.io.AudioSource open(InputStream stream, String hint) throws IOException

      Drives the RIFF parser directly over the caller's InputStream and hands it to WavAudioSource.fromCallerStream(WaveFormat, InputStream) with the stream positioned at the first byte of the data payload. The returned source is stream-backed, so AudioSource.canSeek() is false (Requirement 3.9).

      This method never closes the caller's stream (Requirement 4.10), on either the success path or the failure path: the stream is the caller's to manage. The returned WavAudioSource's close() likewise leaves the stream untouched.

      Specified by:
      open in interface com.tino1b2be.dtmf.io.AudioSourceProvider
      Parameters:
      stream - the caller-supplied stream; must be non-null. Callers that come in through AudioSources.open(InputStream, String) will always receive a markable stream thanks to the facade's buffering (Requirement 5.12); callers invoking the provider directly must supply a stream whose bytes can be consumed forward-only starting from the current position.
      hint - optional caller-supplied hint (file name, URL path segment, MIME type); may be null and is ignored by this provider (content-based detection wins regardless of the hint).
      Returns:
      an opened WavAudioSource in stream-backed mode
      Throws:
      NullPointerException - if stream is null
      com.tino1b2be.dtmf.io.UnsupportedAudioFormatException - on unsupported encodings (Requirement 9.10)
      IOException - on structural defects (Requirement 9.11) or underlying stream errors