/** * Fully copy the remaining bytes of this (decoded PCM sound data samples) stream into the specified {@link OutputStream}, that is, fully decodes the rest of the sound and copies the decoded data into the {@link OutputStream}. * <p> * This method is simply a convenience wrapper for the following code: {@code copy(this, os)}, where {@code copy} is a method that would fully copy a stream into another. * <p> * This method <b>is blocking</b> and the MPEG decoding process <b>might take a long time, e.g. a few seconds for a sample music track</b>. You are encouraged to call this method e.g. from a background thread. * <p> * The exact layout of the PCM data produced by this stream is described in this class documentation. * * @param os The output stream in which to put the decoded raw PCM sound samples, must be non-null. * @return The number of <b>BYTES</b> that were written into the output steam. <b>This is different from the number of samples that were written.</b> * @ */ public int decodeFullyInto(BinaryWriter writer) { Debug.Assert(writer != null); if (index == -1) { return(0); } int remaining = soundData.samplesBuffer.Length - index; if (remaining > 0) { writer.Write(soundData.samplesBuffer, index, remaining); } int read = remaining; while (Mp3Decoder.decodeFrame(soundData)) { writer.Write(soundData.samplesBuffer); read += soundData.samplesBuffer.Length; // Debug.WriteLine(read); } soundData.samplesBuffer = null; index = -1; return(read); }
/** * Creates a new Sound, that will read from the specified encoded MPEG data stream. * <p> * This method will try to read the very beginning of the MPEG stream (i.e. 1 MPEG frame) to get its sampling frequency and various other metadata. <b>A stream containing no MPEG data frames/a zero duration MPEG data source will be considered as invalid and will throw {@link IOException}.</b> * <p> * This method will not read or decode the file fully, which means it doesn't block and is very fast (as opposed to {@link #decodeFullyInto(OutputStream)}; you probably don't need to execute this method in a specific background thread. * <p>It is only when reading from this stream that the decoding process will take place (as you read from the stream). <b>The decoding process is quite CPU-intensive, though, so you are encouraged to use a background thread/other multithreading techniques to read from the stream without blocking the whole application.</b> * <p> * The various metadata methods such as {@link #getSamplingFrequency()} and {@link #isStereo()} may be called as soon as this object is instantiated (i.e. may be called at any time during the object lifetime). * <p> * <b>The data layout is as follows (this is a contract that won't change):</b> * <br>The decoded PCM sound data is stored as a contiguous stream of 16-bit little-endian signed samples (2 bytes per sample). * <ul> * <li>If the sound is in stereo mode, then the samples will be interleaved, e.g. {@code left_sample_0 (2 bytes), right_sample_0 (2 bytes), left_sample_1 (2 bytes), right_sample_1 (2 bytes), ...} * <li>If the sound is in mono mode, then the samples will be contiguous, e.g. {@code sample_0 (2 bytes), sample_1 (2 bytes), ...} * </ul> * @param in The input stream from which to read the encoded MPEG data, must be non-null. * @ */ public Sound(BinaryReader reader) { Debug.Assert(reader != null); soundData = Mp3Decoder.init(reader); if (soundData == null) { throw new IOException("No MPEG data in the specified input stream!"); } }
/** * {@inheritDoc} * <p> * Refer to this class documentation for the decoded data layout. */ public int read(byte[] b, int off, int len) { if (b == null) { throw new Exception(); } else if (off < 0 || len < 0 || len > b.Length - off) { throw new Exception(); } else if (len == 0) { return(0); } if (index == -1) { return(-1); } int len_ = len; while (len > 0) { if (index == soundData.samplesBuffer.Length) { if (!Mp3Decoder.decodeFrame(soundData)) { index = -1; soundData.samplesBuffer = null; return(len_ == len ? -1 : len_ - len); } index = 0; } int remaining = soundData.samplesBuffer.Length - index; if (remaining > 0) { if (remaining >= len) { Array.Copy(soundData.samplesBuffer, index, b, off, len); index += len; return(len_); } Array.Copy(soundData.samplesBuffer, index, b, off, remaining); off += remaining; len -= remaining; index = soundData.samplesBuffer.Length; } } throw new Exception("Shouldn't happen (internal error)"); }
/** * {@inheritDoc} * <p> * Refer to this class documentation for the decoded data layout. */ public int read() { if (index == -1) { return(-1); } if (index == soundData.samplesBuffer.Length) { if (!Mp3Decoder.decodeFrame(soundData)) { index = -1; soundData.samplesBuffer = null; return(-1); } index = 1; return(soundData.samplesBuffer[0] & 0xFF); } return(soundData.samplesBuffer[index++] & 0xFF); }