Class RawPcmAudioSource

java.lang.Object
com.tino1b2be.dtmf.io.RawPcmAudioSource
All Implemented Interfaces:
AudioSource, Closeable, AutoCloseable

public final class RawPcmAudioSource extends Object implements AudioSource
AudioSource wrapping an in-memory byte[] of raw linear PCM bytes with caller-supplied format metadata.

This is the escape hatch for callers who already hold PCM bytes in memory (from an in-process codec, a network stream, a unit-test fixture, or a custom recorder) and want to feed them through the same AudioSource pipeline as file-based callers. It bypasses the AudioSourceProvider SPI; just construct it directly. For the overwhelmingly common PCM16 little-endian signed case, the fromPcm16LittleEndian(byte[], int, int) factory removes the endianness and encoding arguments (Requirement 7.11).

Supported PCM tuples

The constructor accepts any (bitDepth, byteOrder, encoding) triple supported by the shared SampleConversion helper (Requirements 7.7, 7.8):
  • bitDepth ∈ {16, 24, 32, 64}
  • byteOrder ∈ {LITTLE_ENDIAN, BIG_ENDIAN}
  • encoding ∈ {SIGNED_INT, UNSIGNED_INT, IEEE_FLOAT}, with the additional constraint that IEEE_FLOAT requires bitDepth ∈ {32, 64}.

Normalisation

Integer samples are divided by 2^(bitDepth - 1) so that full-scale positive samples map just below +1.0 and full-scale negative samples map exactly to -1.0. IEEE float samples are widened to double without scaling (Requirements 3.6, 7.12). The conversion formulas live in SampleConversion and are shared verbatim with the WAV and MP3 providers.

Buffer ownership — the backing array is NOT copied

The caller-supplied data array is referenced, not copied (Requirement 7.13). The source reads from the array on every read(double[], int, int) call for the lifetime of this object. Mutating data after construction will change the output of subsequent reads and yields undefined behaviour; callers who need to reuse or recycle their byte buffer must pass a defensive copy (typically data.clone()) into the constructor. The cost of zero-copy is entirely on the caller's side; the source pays nothing.

Seekability

canSeek() always returns true (Requirement 7.10); seeking is an O(1) update of the internal frame cursor since the whole buffer is already resident.

Thread safety

Instances are not thread-safe (shared with the general AudioSource contract). The mutable state is the frame cursor and the closed flag; callers that need concurrent access must serialise externally or construct one source per thread sharing the same underlying byte[] (which is safe provided no one mutates the bytes).
Since:
2.1.0
See Also:
  • Constructor Summary

    Constructors
    Constructor
    Description
    RawPcmAudioSource(byte[] data, int sampleRate, int bitDepth, ByteOrder byteOrder, int channelCount, PcmEncoding encoding)
    Wrap a caller-supplied byte buffer of raw linear PCM bytes.
  • Method Summary

    Modifier and Type
    Method
    Description
    int
    Native sample bit depth of the source.
    Return the byte order this source was constructed with.
    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 AudioSource.read(double[], int, int) (Requirement 3.13).
    Return the PCM numeric encoding this source was constructed with.
    fromPcm16LittleEndian(byte[] data, int sampleRate, int channelCount)
    Convenience factory for the overwhelmingly common case of 16-bit little-endian signed PCM (Requirement 7.11).
    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 AudioSource.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.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface com.tino1b2be.dtmf.io.AudioSource

    read
  • Constructor Details

    • RawPcmAudioSource

      public RawPcmAudioSource(byte[] data, int sampleRate, int bitDepth, ByteOrder byteOrder, int channelCount, PcmEncoding encoding)
      Wrap a caller-supplied byte buffer of raw linear PCM bytes.

      The data array is referenced, not copied (Requirement 7.13). See the class Javadoc for the full buffer-ownership contract; mutating data after construction yields undefined read(double[], int, int) output.

      Parameters:
      data - raw PCM byte buffer; length must be an exact multiple of (bitDepth / 8) * channelCount
      sampleRate - source sample rate in Hz; 1 <= sampleRate <= 384000 (Req 7.5)
      bitDepth - native sample bit depth; bitDepth ∈ {16, 24, 32, 64} (Req 7.7)
      byteOrder - byte order of multi-byte samples; ByteOrder.LITTLE_ENDIAN or ByteOrder.BIG_ENDIAN
      channelCount - number of interleaved channels; 1 <= channelCount <= 8 (Req 7.6)
      encoding - PCM numeric encoding (Req 7.3); with the additional constraint that PcmEncoding.IEEE_FLOAT requires bitDepth ∈ {32, 64} (Req 7.8)
      Throws:
      NullPointerException - if data, byteOrder, or encoding is null; the message identifies the offending parameter (Req 7.4)
      IllegalArgumentException - if any numeric parameter is outside its accepted range, if the (encoding, bitDepth) pair is invalid, or if data.length is not an exact multiple of the frame size (Req 7.5, 7.6, 7.7, 7.8, 7.9); the message identifies the offending value and the valid range/set
  • Method Details

    • fromPcm16LittleEndian

      public static RawPcmAudioSource fromPcm16LittleEndian(byte[] data, int sampleRate, int channelCount)
      Convenience factory for the overwhelmingly common case of 16-bit little-endian signed PCM (Requirement 7.11). Equivalent to
      
         new RawPcmAudioSource(data, sampleRate, 16,
                 ByteOrder.LITTLE_ENDIAN, channelCount, PcmEncoding.SIGNED_INT)
       

      The data array is referenced, not copied; the same buffer-ownership contract as the primary constructor applies (Requirement 7.13).

      Parameters:
      data - raw PCM16 byte buffer; length must be an exact multiple of 2 * channelCount
      sampleRate - source sample rate in Hz
      channelCount - number of interleaved channels
      Returns:
      a new RawPcmAudioSource configured for PCM16 LE signed int
      Throws:
      NullPointerException - if data is null
      IllegalArgumentException - if any constructor validation rule is violated
    • sampleRate

      public int sampleRate()
      Description copied from interface: AudioSource
      Sample rate of the source in Hertz.
      Specified by:
      sampleRate in interface AudioSource
      Returns:
      sample rate in Hz; always strictly positive (Requirement 3.2)
    • channelCount

      public int channelCount()
      Description copied from interface: AudioSource
      Channel count of the source.
      Specified by:
      channelCount in interface AudioSource
      Returns:
      channel count; always strictly positive (Requirement 3.3). 1 for mono, 2 for stereo, and so on.
    • bitDepth

      public int bitDepth()
      Description copied from interface: AudioSource
      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 AudioSource.read(double[], int, int) is performed by the source: integer samples are divided by 2^(bitDepth - 1); IEEE float samples are widened without scaling.

      Specified by:
      bitDepth in interface AudioSource
      Returns:
      native sample bit depth; one of the values listed above
    • totalFrames

      public long totalFrames()
      Description copied from interface: AudioSource
      Total number of sample frames available from the source, or -1L if the total is unknown.

      A seekable source (see AudioSource.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).

      Specified by:
      totalFrames in interface AudioSource
      Returns:
      total frame count, or -1L if unknown
    • canSeek

      public boolean canSeek()
      Description copied from interface: AudioSource
      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).

      Specified by:
      canSeek in interface AudioSource
      Returns:
      true if AudioSource.seek(long) is supported
    • currentFrame

      public long currentFrame()
      Description copied from interface: AudioSource
      Zero-based index of the next frame that will be returned by AudioSource.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 AudioSource.seek(long) to f, it equals f. At end of stream it equals AudioSource.totalFrames() when that value is known.

      Specified by:
      currentFrame in interface AudioSource
      Returns:
      zero-based frame index of the next frame to be read
    • encoding

      public PcmEncoding encoding()
      Return the PCM numeric encoding this source was constructed with. Informational; the decoded samples handed back from read(double[], int, int) are always normalised double regardless.
      Returns:
      the PcmEncoding the source decodes from
    • byteOrder

      public ByteOrder byteOrder()
      Return the byte order this source was constructed with. Informational.
      Returns:
      the ByteOrder the source decodes from
    • read

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

      When AudioSource.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.

      Implementation notes:

      • Reads up to length frames (not samples) into buffer starting at offset. Exactly n * channelCount samples are written when this call returns n >= 0, with channels interleaved per the AudioSource contract (Requirement 3.7).
      • Returns -1 when the source is already exhausted at entry (Requirement 3.6).
      • Reads are served directly out of the backing byte array; no intermediate buffer allocations occur.
      Specified by:
      read in interface AudioSource
      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)
      NullPointerException - if buffer is null
      IndexOutOfBoundsException - if offset < 0, length < 0, or offset + length * channelCount exceeds buffer.length
    • seek

      public void seek(long frameIndex) throws IOException
      Reposition the read cursor so the next AudioSource.read(double[], int, int) returns frames starting at frameIndex from the start of the source (Requirement 3.10).

      RawPcmAudioSource always supports seeking (Requirement 7.10); canSeek() is true. The valid range is [0, totalFrames()]; seeking to totalFrames() positions the cursor at end-of-stream so the next read(...) returns -1.

      Specified by:
      seek in interface AudioSource
      Parameters:
      frameIndex - zero-based frame index to reposition to
      Throws:
      IOException - if the source has been closed (Requirement 3.14)
      IllegalArgumentException - if frameIndex is outside [0, totalFrames()]; the message identifies the offending value and the valid range (Requirement 3.12)
    • close

      public void close()
      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 AudioSource.read(double[], int, int), AudioSource.read(double[]), or AudioSource.seek(long) throws IOException identifying the source as closed (Requirement 3.14).

      RawPcmAudioSource owns no native resources; close() simply flips an internal flag that causes subsequent read(double[], int, int) or seek(long) calls to throw IOException (Requirement 3.14). It does not touch the caller-supplied backing byte array; callers remain free to reuse or discard it afterwards.

      This method is idempotent: a second and subsequent invocation is a no-op.

      Specified by:
      close in interface AudioSource
      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable