Interface AudioSource

All Superinterfaces:
AutoCloseable, Closeable
All Known Implementing Classes:
RawPcmAudioSource

public interface AudioSource extends Closeable
Pull-based read interface returned by every AudioSourceProvider and by RawPcmAudioSource. Samples read through this interface are always normalised to [-1.0, 1.0], regardless of the underlying format's native encoding (Requirement 3.6). The shape deliberately mirrors InputStream and javax.sound.sampled.AudioInputStream: callers invoke read(double[], int, int) and receive either a frame count or -1 at end of stream.

This is the one interface that unifies WAV, MP3, raw PCM, and any future provider module. Format-specific decoding logic lives entirely inside the implementation; callers see the same (sampleRate, channelCount, bitDepth, totalFrames) metadata and the same interleaved normalised double[] output regardless of which provider served them.

Metadata invariants

  • sampleRate() is strictly positive (Requirement 3.2).
  • channelCount() is strictly positive (Requirement 3.3).
  • bitDepth() is one of {16, 24, 32, 64} for PCM integer sources, {32} for 32-bit IEEE float, {64} for 64-bit IEEE double (Requirement 3.4). MP3 sources report 16 because the JLayer-based provider decodes to 16-bit PCM.
  • totalFrames() returns a non-negative frame count, or -1L when the total is not known up front (typical for forward-only streams such as MP3) (Requirement 3.5).

Channel interleaving

When channelCount() is 1, the output buffer holds one sample per frame. When it is 2, frame k lives at buffer[offset + 2k] (left) and buffer[offset + 2k + 1] (right). For more than two channels, the file's native channel order follows right (Requirement 3.7). This matches the shape com.tino1b2be.dtmf.DtmfDecoder already consumes for ChannelMode.STEREO_INDEPENDENT.

Return-code conventions

read(double[], int, int) returns a non-negative frame count on success, or -1 once the source is exhausted and no more samples will ever be produced (Requirement 3.6). A return value of 0 is not end of stream: it means "no samples available right now, try again." Callers should treat 0 as a retry signal and only treat -1 as terminal.

Buffer ownership

read(double[], int, int) writes into the caller-supplied double[] and must not retain a reference to it after the call returns (Requirement 3.15). Callers are free to reuse, reallocate, or overwrite the buffer between reads; implementations that cache samples internally must copy into their own storage.

Stream ownership on close

close() releases any resources the source owns — file channels it opened, decoder state, native handles — and is idempotent: a second call is a no-op (Requirement 3.14 implies this; the stream contract on Closeable codifies it). A source obtained via AudioSources.open(java.io.InputStream, String) only closes streams the provider wrapped or opened itself; it does not close caller-supplied InputStreams (Requirement 4.10). A source obtained via AudioSources.open(java.nio.file.Path) or AudioSources.open(java.net.URL) owns the stream it opened and will close it on close().

Thread safety

Instances are not thread-safe. A single AudioSource must be driven by at most one thread at a time; callers that need concurrent access must either serialise externally or open one source per thread. Provider implementations are free to assume single-threaded access to their internal state.

Lifecycle after close

Once close() has returned, any subsequent call to read(double[], int, int), read(double[]), or seek(long) throws IOException identifying the source as closed (Requirement 3.14). close() itself remains callable and is a no-op on subsequent invocations.
Since:
2.1.0
See Also:
  • Method Summary

    Modifier and Type
    Method
    Description
    int
    Native sample bit depth of the source.
    boolean
    Whether random-access seeking is supported on this source.
    int
    Channel count of the source.
    void
    Release any resources the source owns.
    long
    Zero-based index of the next frame that will be returned by read(double[], int, int) (Requirement 3.13).
    default int
    read(double[] buffer)
    Read up to buffer.length / channelCount() frames into buffer, starting at index 0.
    int
    read(double[] buffer, int offset, int length)
    Read up to length sample frames into buffer starting at offset.
    int
    Sample rate of the source in Hertz.
    void
    seek(long frameIndex)
    Reposition the read cursor so the next read(double[], int, int) returns frames starting at frameIndex from the start of the source (Requirement 3.10).
    long
    Total number of sample frames available from the source, or -1L if the total is unknown.
  • Method Details

    • sampleRate

      int sampleRate()
      Sample rate of the source in Hertz.
      Returns:
      sample rate in Hz; always strictly positive (Requirement 3.2)
    • channelCount

      int channelCount()
      Channel count of the source.
      Returns:
      channel count; always strictly positive (Requirement 3.3). 1 for mono, 2 for stereo, and so on.
    • bitDepth

      int bitDepth()
      Native sample bit depth of the source.

      The value is one of {16, 24, 32, 64} for PCM integer sources, {32} for 32-bit IEEE float sources, and {64} for 64-bit IEEE double sources (Requirement 3.4). MP3 sources report 16 because the JLayer-based decoder emits 16-bit signed PCM.

      Normalisation to the [-1.0, 1.0] double samples returned from read(double[], int, int) is performed by the source: integer samples are divided by 2^(bitDepth - 1); IEEE float samples are widened without scaling.

      Returns:
      native sample bit depth; one of the values listed above
    • totalFrames

      long totalFrames()
      Total number of sample frames available from the source, or -1L if the total is unknown.

      A seekable source (see canSeek()) that knows its length up front returns a non-negative count here; a forward-only stream whose length is not recoverable without scanning the whole input (a VBR MP3 without a Xing header, for example) returns -1L (Requirement 3.5).

      Returns:
      total frame count, or -1L if unknown
    • read

      int read(double[] buffer, int offset, int length) throws IOException
      Read up to length sample frames into buffer starting at offset.

      When channelCount() is greater than 1, channels are interleaved in the output buffer in the order left, right, then any additional channels in the source's native channel order (Requirement 3.7). Exactly n * channelCount() samples are written when this call returns n >= 0.

      Samples are normalised to [-1.0, 1.0]: integer samples are divided by 2^(bitDepth - 1); IEEE float samples are widened to double without scaling (Requirement 3.6).

      Return conventions:

      • A non-negative return value is the number of frames actually written; it is always in [0, length].
      • -1 means the source is exhausted and no further frames will ever be produced (Requirement 3.6).
      • 0 is not end of stream. It means "no frames available right now" and is a valid, retry-able return value; callers should re-invoke rather than treating it as terminal.

      The caller owns buffer. Implementations do not retain a reference to it after the call returns (Requirement 3.15); callers are free to reuse, reallocate, or overwrite it between reads.

      Parameters:
      buffer - destination buffer; non-null
      offset - starting index into buffer; 0 <= offset <= buffer.length
      length - maximum number of frames to write; the buffer must hold at least length * channelCount() samples starting at offset
      Returns:
      number of frames actually read (0 <= n <= length), or -1 at end of stream
      Throws:
      IOException - if the source has been closed (Requirement 3.14) or an underlying I/O error occurs
    • read

      default int read(double[] buffer) throws IOException
      Read up to buffer.length / channelCount() frames into buffer, starting at index 0. Behaves identically to read(buffer, 0, buffer.length) (Requirement 3.8); kept on the interface as a default so implementations only have to supply the three-argument form.
      Parameters:
      buffer - destination buffer; non-null
      Returns:
      number of frames actually read, or -1 at end of stream
      Throws:
      IOException - if the source has been closed or an underlying I/O error occurs
      NullPointerException - if buffer is null
    • canSeek

      boolean canSeek()
      Whether random-access seeking is supported on this source.

      Seekable sources (file-backed WAV, RawPcmAudioSource) return true; forward-only sources (MP3, any InputStream-backed source) return false (Requirement 3.9).

      Returns:
      true if seek(long) is supported
    • seek

      void seek(long frameIndex) throws IOException
      Reposition the read cursor so the next read(double[], int, int) returns frames starting at frameIndex from the start of the source (Requirement 3.10).
      Parameters:
      frameIndex - zero-based frame index to reposition to
      Throws:
      UnsupportedOperationException - if canSeek() returns false; the exception message identifies the implementing class (Requirement 3.11)
      IllegalArgumentException - if frameIndex < 0, or if totalFrames() is non-negative and frameIndex > totalFrames(); the exception message identifies the offending value and the valid range (Requirement 3.12)
      IOException - if the source has been closed (Requirement 3.14) or an underlying I/O error occurs
    • currentFrame

      long currentFrame()
      Zero-based index of the next frame that will be returned by read(double[], int, int) (Requirement 3.13).

      On a freshly opened source the return value is 0. After a successful read that returned n frames, it increases by n. After a successful seek(long) to f, it equals f. At end of stream it equals totalFrames() when that value is known.

      Returns:
      zero-based frame index of the next frame to be read
    • close

      void close() throws IOException
      Release any resources the source owns.

      Closing a source obtained via AudioSources.open(java.nio.file.Path) or AudioSources.open(java.net.URL) also closes the underlying stream that AudioSources opened on the caller's behalf. Closing a source obtained via AudioSources.open(java.io.InputStream, String) does not close the caller-supplied stream (Requirement 4.10); the caller retains ownership of any InputStream they handed in.

      This method is idempotent: a second and subsequent invocation is a no-op. After close() has returned, any call to read(double[], int, int), read(double[]), or seek(long) throws IOException identifying the source as closed (Requirement 3.14).

      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Throws:
      IOException - if releasing the underlying resources fails